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:
2026-10-16 18:24:16 +01:00
committed by andy
parent 2c3fb146ab
commit 9f22b522df
1996 changed files with 1992 additions and 1179 deletions
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "5.0.5",
"commands": [
"dotnet-ef"
]
}
}
}
@@ -0,0 +1,938 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="account.activate.caption" xml:space="preserve">
<value>Account Activation</value>
</data>
<data name="account.activate.email.subject" xml:space="preserve">
<value>Account Activation</value>
</data>
<data name="account.activate.info" xml:space="preserve">
<value>An activation email will be sent to the selected user inviting them to assign a password and access the platform.</value>
</data>
<data name="account.activate.notify" xml:space="preserve">
<value>Member account has been activated and welcome email sent.</value>
</data>
<data name="account.caption.image.upload" xml:space="preserve">
<value>Image Upload</value>
</data>
<data name="account.caption.profile" xml:space="preserve">
<value>My Profile</value>
</data>
<data name="account.caption.profile.contact" xml:space="preserve">
<value>My Details</value>
</data>
<data name="account.caption.profile.eligibility" xml:space="preserve">
<value>Eligibility</value>
</data>
<data name="account.caption.profile.eligibility.add" xml:space="preserve">
<value>Add Eligibility</value>
</data>
<data name="account.caption.profile.eligibility.remove" xml:space="preserve">
<value>Remove Eligibility</value>
</data>
<data name="account.caption.profile.eligibility.update" xml:space="preserve">
<value>Update Eligibility</value>
</data>
<data name="account.caption.profile.organisations" xml:space="preserve">
<value>My Organisations</value>
</data>
<data name="account.caption.profile.sport" xml:space="preserve">
<value>My Sport</value>
</data>
<data name="account.caption.profile.sport.add" xml:space="preserve">
<value>Add Sport</value>
</data>
<data name="account.caption.profile.sport.update" xml:space="preserve">
<value>My Sport</value>
</data>
<data name="account.caption.profile.sports" xml:space="preserve">
<value>My Sports</value>
</data>
<data name="account.caption.profile.sports.experience" xml:space="preserve">
<value>Sport Experience</value>
</data>
<data name="activity.caption.activities" xml:space="preserve">
<value>Activities</value>
</data>
<data name="activity.caption.activity" xml:space="preserve">
<value>Activity</value>
</data>
<data name="article.caption.article" xml:space="preserve">
<value>Article</value>
</data>
<data name="article.caption.article.create" xml:space="preserve">
<value>Create Article</value>
</data>
<data name="article.caption.article.update" xml:space="preserve">
<value>Update Article</value>
</data>
<data name="article.caption.articles" xml:space="preserve">
<value>Articles</value>
</data>
<data name="caption.contact.method" xml:space="preserve">
<value>Contact Method</value>
</data>
<data name="caption.feedback" xml:space="preserve">
<value>Feedback</value>
</data>
<data name="caption.member.create" xml:space="preserve">
<value>Add Member</value>
</data>
<data name="caption.member.update" xml:space="preserve">
<value>Update Member</value>
</data>
<data name="caption.my.eligibility" xml:space="preserve">
<value>My Eligibility</value>
</data>
<data name="caption.show.info" xml:space="preserve">
<value>Show info</value>
</data>
<data name="caption.user.config" xml:space="preserve">
<value>User Configuration</value>
</data>
<data name="caption.user.report.create" xml:space="preserve">
<value>Create User Report</value>
</data>
<data name="caption.user.report.update" xml:space="preserve">
<value>Update User Report</value>
</data>
<data name="caption.version.history" xml:space="preserve">
<value>Version History</value>
</data>
<data name="company.name" xml:space="preserve">
<value>Solum Forti</value>
</data>
<data name="copyright.symbol.year.html" xml:space="preserve">
<value>&amp;copy; {0}</value>
</data>
<data name="dialog.title.alert" xml:space="preserve">
<value>Alert</value>
</data>
<data name="dialog.title.confirm" xml:space="preserve">
<value>Confirm</value>
</data>
<data name="dialog.title.progress" xml:space="preserve">
<value>Progress</value>
</data>
<data name="dialog.title.signout" xml:space="preserve">
<value>Sign-out</value>
</data>
<data name="home.caption.activities" xml:space="preserve">
<value>Activities</value>
</data>
<data name="home.caption.application" xml:space="preserve">
<value>Online</value>
</data>
<data name="home.caption.home" xml:space="preserve">
<value>Home</value>
</data>
<data name="home.caption.welcome" xml:space="preserve">
<value>Welcome</value>
</data>
<data name="home.organisation.request.action" xml:space="preserve">
<value>Register Your Organisation</value>
</data>
<data name="home.organisation.request.para1" xml:space="preserve">
<value>Do you belong to a club or association that would benefit from participating on this platform?</value>
</data>
<data name="home.organisation.request.para2" xml:space="preserve">
<value>Sign-up now with a few details and we'll let you know when you can login and get going.</value>
</data>
<data name="info.char.available" xml:space="preserve">
<value>[1 char available]</value>
</data>
<data name="info.chars.available" xml:space="preserve">
<value>[{0} chars available]</value>
</data>
<data name="info.profile.eligibility" xml:space="preserve">
<value>You can identify the countries that you are eligible to participate for by adding references to your parentage and passport/visas.</value>
</data>
<data name="label. registered" xml:space="preserve">
<value>Registered</value>
</data>
<data name="label.about" xml:space="preserve">
<value>About</value>
</data>
<data name="label.account" xml:space="preserve">
<value>My Account</value>
</data>
<data name="label.actions" xml:space="preserve">
<value>Actions</value>
</data>
<data name="label.activate" xml:space="preserve">
<value>Activate</value>
</data>
<data name="label.add" xml:space="preserve">
<value>Add</value>
</data>
<data name="label.address" xml:space="preserve">
<value>Address</value>
</data>
<data name="label.address.lines" xml:space="preserve">
<value>Address lines</value>
</data>
<data name="label.alternatively" xml:space="preserve">
<value>Alternatively</value>
</data>
<data name="label.any" xml:space="preserve">
<value>Any</value>
</data>
<data name="label.application" xml:space="preserve">
<value>Application</value>
</data>
<data name="label.article" xml:space="preserve">
<value>Article</value>
</data>
<data name="label.associations" xml:space="preserve">
<value>Associations</value>
</data>
<data name="label.at" xml:space="preserve">
<value>At</value>
</data>
<data name="label.average" xml:space="preserve">
<value>Average</value>
</data>
<data name="label.awards" xml:space="preserve">
<value>Awards</value>
</data>
<data name="label.bio" xml:space="preserve">
<value>Bio</value>
</data>
<data name="label.calendar" xml:space="preserve">
<value>Calendar</value>
</data>
<data name="label.cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="label.category" xml:space="preserve">
<value>Category</value>
</data>
<data name="label.clear" xml:space="preserve">
<value>Clear</value>
</data>
<data name="label.close" xml:space="preserve">
<value>Close</value>
</data>
<data name="label.club" xml:space="preserve">
<value>Club</value>
</data>
<data name="label.comment" xml:space="preserve">
<value>Comment</value>
</data>
<data name="label.contact" xml:space="preserve">
<value>Contact</value>
</data>
<data name="label.contact.details" xml:space="preserve">
<value>Contact details</value>
</data>
<data name="label.contact.method" xml:space="preserve">
<value>Contact method</value>
</data>
<data name="label.contacts" xml:space="preserve">
<value>Contacts</value>
</data>
<data name="label.copy" xml:space="preserve">
<value>Copy</value>
</data>
<data name="label.country" xml:space="preserve">
<value>Country</value>
</data>
<data name="label.create" xml:space="preserve">
<value>Create</value>
</data>
<data name="label.created" xml:space="preserve">
<value>Created</value>
</data>
<data name="label.date" xml:space="preserve">
<value>Date</value>
</data>
<data name="label.date.of.birth" xml:space="preserve">
<value>Date of birth</value>
</data>
<data name="label.day" xml:space="preserve">
<value>Day</value>
</data>
<data name="label.delete" xml:space="preserve">
<value>Delete</value>
</data>
<data name="label.details" xml:space="preserve">
<value>Details</value>
</data>
<data name="label.discipline" xml:space="preserve">
<value>Discipline</value>
</data>
<data name="label.download" xml:space="preserve">
<value>Download</value>
</data>
<data name="label.drag.image.here" xml:space="preserve">
<value>Drag your image here.</value>
</data>
<data name="label.duration" xml:space="preserve">
<value>Duration</value>
</data>
<data name="label.edit" xml:space="preserve">
<value>Edit</value>
</data>
<data name="label.effective" xml:space="preserve">
<value>Effective</value>
</data>
<data name="label.eligibility" xml:space="preserve">
<value>Eligibility</value>
</data>
<data name="label.email" xml:space="preserve">
<value>Email</value>
</data>
<data name="label.email.address" xml:space="preserve">
<value>Email address</value>
</data>
<data name="label.ends" xml:space="preserve">
<value>Ends</value>
</data>
<data name="label.events" xml:space="preserve">
<value>Events</value>
</data>
<data name="label.excellent" xml:space="preserve">
<value>Excellent</value>
</data>
<data name="label.experience" xml:space="preserve">
<value>Experience</value>
</data>
<data name="label.expires" xml:space="preserve">
<value>Expires</value>
</data>
<data name="label.expires.on" xml:space="preserve">
<value>Expires on</value>
</data>
<data name="label.expiry" xml:space="preserve">
<value>Expiry</value>
</data>
<data name="label.facebook" xml:space="preserve">
<value>Facebook</value>
</data>
<data name="label.facilities" xml:space="preserve">
<value>Facilities</value>
</data>
<data name="label.feedback" xml:space="preserve">
<value>Feedback</value>
</data>
<data name="label.finishes" xml:space="preserve">
<value>Finishes</value>
</data>
<data name="label.first" xml:space="preserve">
<value>First</value>
</data>
<data name="label.first.name" xml:space="preserve">
<value>First name</value>
</data>
<data name="label.follow" xml:space="preserve">
<value>Follow</value>
</data>
<data name="label.forgotten.password" xml:space="preserve">
<value>Forgotten password?</value>
</data>
<data name="label.from" xml:space="preserve">
<value>From</value>
</data>
<data name="label.gender" xml:space="preserve">
<value>Gender</value>
</data>
<data name="label.good" xml:space="preserve">
<value>Good</value>
</data>
<data name="label.headline" xml:space="preserve">
<value>Headline</value>
</data>
<data name="label.home" xml:space="preserve">
<value>Home</value>
</data>
<data name="label.image" xml:space="preserve">
<value>Image</value>
</data>
<data name="label.info" xml:space="preserve">
<value>Info</value>
</data>
<data name="label.keep.signed.in" xml:space="preserve">
<value>Remember Me</value>
</data>
<data name="label.last" xml:space="preserve">
<value>Last</value>
</data>
<data name="label.last.name" xml:space="preserve">
<value>Last name</value>
</data>
<data name="label.league" xml:space="preserve">
<value>League</value>
</data>
<data name="label.location" xml:space="preserve">
<value>Location</value>
</data>
<data name="label.logo" xml:space="preserve">
<value>Logo</value>
</data>
<data name="label.main" xml:space="preserve">
<value>Main</value>
</data>
<data name="label.member" xml:space="preserve">
<value>Member</value>
</data>
<data name="label.members" xml:space="preserve">
<value>Members</value>
</data>
<data name="label.method" xml:space="preserve">
<value>Method</value>
</data>
<data name="label.mobile" xml:space="preserve">
<value>Mobile</value>
</data>
<data name="label.month" xml:space="preserve">
<value>Month</value>
</data>
<data name="label.my.organisations" xml:space="preserve">
<value>My Organisations</value>
</data>
<data name="label.my.sports" xml:space="preserve">
<value>My Sports</value>
</data>
<data name="label.name" xml:space="preserve">
<value>Name</value>
</data>
<data name="label.next" xml:space="preserve">
<value>Next</value>
</data>
<data name="label.no" xml:space="preserve">
<value>No</value>
</data>
<data name="label.not.specified" xml:space="preserve">
<value>Not specified</value>
</data>
<data name="label.occurs" xml:space="preserve">
<value>Occurs</value>
</data>
<data name="label.office" xml:space="preserve">
<value>Office</value>
</data>
<data name="label.officiating" xml:space="preserve">
<value>Officiating</value>
</data>
<data name="label.ok" xml:space="preserve">
<value>OK</value>
</data>
<data name="label.on" xml:space="preserve">
<value>On</value>
</data>
<data name="label.opportunities" xml:space="preserve">
<value>Opportunities</value>
</data>
<data name="label.opportunity" xml:space="preserve">
<value>Opportunity</value>
</data>
<data name="label.organisation" xml:space="preserve">
<value>Organisation</value>
</data>
<data name="label.organsiation.details" xml:space="preserve">
<value>Organisation details</value>
</data>
<data name="label.outline" xml:space="preserve">
<value>Outline</value>
</data>
<data name="label.page.info" xml:space="preserve">
<value>Page {0} of {1}</value>
</data>
<data name="label.participating" xml:space="preserve">
<value>Participating</value>
</data>
<data name="label.password" xml:space="preserve">
<value>Password</value>
</data>
<data name="label.password.confirm" xml:space="preserve">
<value>Confirm password</value>
</data>
<data name="label.password.current" xml:space="preserve">
<value>Current password</value>
</data>
<data name="label.period" xml:space="preserve">
<value>Period</value>
</data>
<data name="label.phone" xml:space="preserve">
<value>Phone</value>
</data>
<data name="label.position" xml:space="preserve">
<value>Position</value>
</data>
<data name="label.postal.code" xml:space="preserve">
<value>Postal code</value>
</data>
<data name="label.preparing" xml:space="preserve">
<value>Preparing</value>
</data>
<data name="label.previous" xml:space="preserve">
<value>Previous</value>
</data>
<data name="label.province" xml:space="preserve">
<value>Province</value>
</data>
<data name="label.publication" xml:space="preserve">
<value>Publication</value>
</data>
<data name="label.publish" xml:space="preserve">
<value>Publish</value>
</data>
<data name="label.published" xml:space="preserve">
<value>Published</value>
</data>
<data name="label.raised" xml:space="preserve">
<value>Raised</value>
</data>
<data name="label.raised.by" xml:space="preserve">
<value>Raised by</value>
</data>
<data name="label.reference" xml:space="preserve">
<value>Reference</value>
</data>
<data name="label.refresh" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="label.region" xml:space="preserve">
<value>Region</value>
</data>
<data name="label.region.selected" xml:space="preserve">
<value>Selected region</value>
</data>
<data name="label.regions.available" xml:space="preserve">
<value>Available regions</value>
</data>
<data name="label.remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="label.report" xml:space="preserve">
<value>Report</value>
</data>
<data name="label.roles" xml:space="preserve">
<value>Roles</value>
</data>
<data name="label.save" xml:space="preserve">
<value>Save</value>
</data>
<data name="label.search" xml:space="preserve">
<value>Search</value>
</data>
<data name="label.security" xml:space="preserve">
<value>Security</value>
</data>
<data name="label.select" xml:space="preserve">
<value>Select</value>
</data>
<data name="label.selected.count" xml:space="preserve">
<value>{0} selected</value>
</data>
<data name="label.selected.none" xml:space="preserve">
<value>none selected</value>
</data>
<data name="label.speciality" xml:space="preserve">
<value>Speciality</value>
</data>
<data name="label.sport" xml:space="preserve">
<value>Sport</value>
</data>
<data name="label.sport.selected" xml:space="preserve">
<value>Selected sport</value>
</data>
<data name="label.sports.available" xml:space="preserve">
<value>Available sports</value>
</data>
<data name="label.starts" xml:space="preserve">
<value>Starts</value>
</data>
<data name="label.strength" xml:space="preserve">
<value>Strength</value>
</data>
<data name="label.summary" xml:space="preserve">
<value>Summary</value>
</data>
<data name="label.support" xml:space="preserve">
<value>Support</value>
</data>
<data name="label.team" xml:space="preserve">
<value>Team</value>
</data>
<data name="label.time" xml:space="preserve">
<value>Time</value>
</data>
<data name="label.title" xml:space="preserve">
<value>Title</value>
</data>
<data name="label.to" xml:space="preserve">
<value>To</value>
</data>
<data name="label.town" xml:space="preserve">
<value>Town</value>
</data>
<data name="label.twitter" xml:space="preserve">
<value>Twitter</value>
</data>
<data name="label.type" xml:space="preserve">
<value>Type</value>
</data>
<data name="label.until" xml:space="preserve">
<value>Until</value>
</data>
<data name="label.update" xml:space="preserve">
<value>Update</value>
</data>
<data name="label.upload" xml:space="preserve">
<value>Upload</value>
</data>
<data name="label.user" xml:space="preserve">
<value>User</value>
</data>
<data name="label.users" xml:space="preserve">
<value>Users</value>
</data>
<data name="label.value" xml:space="preserve">
<value>Value</value>
</data>
<data name="label.very.good" xml:space="preserve">
<value>Very Good</value>
</data>
<data name="label.view" xml:space="preserve">
<value>View</value>
</data>
<data name="label.views" xml:space="preserve">
<value>Views</value>
</data>
<data name="label.website" xml:space="preserve">
<value>Website</value>
</data>
<data name="label.wikipedia" xml:space="preserve">
<value>Wikipedia</value>
</data>
<data name="label.year" xml:space="preserve">
<value>Year</value>
</data>
<data name="label.yes" xml:space="preserve">
<value>Yes</value>
</data>
<data name="msg.confirm.item.remove" xml:space="preserve">
<value>Are you sure that this item should be removed?</value>
</data>
<data name="msg.data.noneFound" xml:space="preserve">
<value>No matching data has been found.</value>
</data>
<data name="msg.data.querying" xml:space="preserve">
<value>Querying data. Please wait ...</value>
</data>
<data name="msg.data.refreshing" xml:space="preserve">
<value>Refreshing data. Please wait ...</value>
</data>
<data name="msg.error.item.delete.failed" xml:space="preserve">
<value>Item deletion failed</value>
</data>
<data name="msg.error.item.duplicate" xml:space="preserve">
<value>A duplicate item already exists.</value>
</data>
<data name="msg.error.item.id.not.found" xml:space="preserve">
<value>Item not found for specified Id</value>
</data>
<data name="msg.error.item.id.not.specified" xml:space="preserve">
<value>Item Id not specified</value>
</data>
<data name="msg.error.item.insert.failed" xml:space="preserve">
<value>Item creation failed</value>
</data>
<data name="msg.error.item.update.failed" xml:space="preserve">
<value>Item update failed</value>
</data>
<data name="msg.error.system" xml:space="preserve">
<value>System error encountered.</value>
</data>
<data name="msg.item.created" xml:space="preserve">
<value>Item has been created.</value>
</data>
<data name="msg.item.removed" xml:space="preserve">
<value>Item has been removed.</value>
</data>
<data name="msg.item.updated" xml:space="preserve">
<value>Item has been updated.</value>
</data>
<data name="msg.organisation.request.success" xml:space="preserve">
<value>Thank you for registering your organisation.
You will be contacted by email once the details have been confirmed.
You will then be able to publicise your sporting activities.</value>
</data>
<data name="msg.something.gone.wrong" xml:space="preserve">
<value>Unfortunately, something has gone wrong.\n\nYou will be returned to the home page.</value>
</data>
<data name="msg.upload.file.select" xml:space="preserve">
<value>Please select a file to upload.</value>
</data>
<data name="msg.upload.file.size.max" xml:space="preserve">
<value>Maximum image file size is {0}.</value>
</data>
<data name="msg.upload.file.type.invalid" xml:space="preserve">
<value>Unsupported file type: {0}.</value>
</data>
<data name="msg.upload.file.uploading" xml:space="preserve">
<value>Uploading {0}.</value>
</data>
<data name="msg.upload.image.invalid" xml:space="preserve">
<value>Invalid image file uploaded</value>
</data>
<data name="msg.upload.image.invalid.content.type" xml:space="preserve">
<value>Invalid image content type.</value>
</data>
<data name="msg.upload.image.invalid.mime" xml:space="preserve">
<value>Invalid image mime type.</value>
</data>
<data name="msg.upload.image.resize.failed" xml:space="preserve">
<value>Image resize failed.</value>
</data>
<data name="msg.upload.image.single" xml:space="preserve">
<value>Only one image can be uploaded.</value>
</data>
<data name="msg.validation.duplicate.value" xml:space="preserve">
<value>Value is already in use</value>
</data>
<data name="msg.validation.invalid.length" xml:space="preserve">
<value>Value must be {0} characters</value>
</data>
<data name="msg.validation.invalid.value" xml:space="preserve">
<value>Invalid value</value>
</data>
<data name="msg.validation.mandatory.value" xml:space="preserve">
<value>Mandatory value</value>
</data>
<data name="msg.validation.maximum.length" xml:space="preserve">
<value>Value must not exceed {0} characters</value>
</data>
<data name="msg.warn.password.invalid" xml:space="preserve">
<value>Password must be a minimum of {0} characters with upper and lower case letters, at least one number and special character unless at least {1} characters long.</value>
</data>
<data name="organisation.caption.event" xml:space="preserve">
<value>Event</value>
</data>
<data name="organisation.caption.event.create" xml:space="preserve">
<value>Create Event</value>
</data>
<data name="organisation.caption.event.update" xml:space="preserve">
<value>Update event</value>
</data>
<data name="organisation.caption.events" xml:space="preserve">
<value>Events</value>
</data>
<data name="organisation.caption.opportunities" xml:space="preserve">
<value>Organisation Opportunities</value>
</data>
<data name="organisation.caption.opportunity" xml:space="preserve">
<value>Organisation Opportunity</value>
</data>
<data name="organisation.caption.opportunity.create" xml:space="preserve">
<value>Create Opportunity</value>
</data>
<data name="organisation.caption.opportunity.update" xml:space="preserve">
<value>Update Opportunity</value>
</data>
<data name="organisation.caption.organisation.request" xml:space="preserve">
<value>Request Organisation</value>
</data>
<data name="organisation.caption.organisation.select" xml:space="preserve">
<value>Select Organisation</value>
</data>
<data name="organisation.caption.organisations" xml:space="preserve">
<value>Organisations</value>
</data>
<data name="organisation.member.remove" xml:space="preserve">
<value>Remove Member</value>
</data>
<data name="organisation.member.remove.confirm" xml:space="preserve">
<value>Are you sure that this member should be removed from the organisation?</value>
</data>
<data name="organisation.member.select.roles" xml:space="preserve">
<value>Member Roles</value>
</data>
<data name="organisation.members" xml:space="preserve">
<value>Organisation Members</value>
</data>
<data name="organisation.members.msg.remove.own.admin.role" xml:space="preserve">
<value>You cannot remove your own 'Member Admin' role.</value>
</data>
<data name="policies.caption.privacy" xml:space="preserve">
<value>Privacy</value>
</data>
<data name="policies.caption.privacy.policy" xml:space="preserve">
<value>Privacy Policy</value>
</data>
<data name="reference.caption.country.select" xml:space="preserve">
<value>Select Country</value>
</data>
<data name="reference.caption.province.select" xml:space="preserve">
<value>Select Province</value>
</data>
<data name="reference.caption.region.select" xml:space="preserve">
<value>Select Region</value>
</data>
<data name="reference.caption.regions" xml:space="preserve">
<value>Regions</value>
</data>
<data name="reference.caption.sport.select" xml:space="preserve">
<value>Select Sport</value>
</data>
<data name="security.caption.change.password" xml:space="preserve">
<value>Change Password</value>
</data>
<data name="security.caption.request.password" xml:space="preserve">
<value>Request Password</value>
</data>
<data name="security.caption.signin" xml:space="preserve">
<value>Sign-in</value>
</data>
<data name="security.caption.signout" xml:space="preserve">
<value>Sign-out</value>
</data>
<data name="security.change.password.current.invalid" xml:space="preserve">
<value>Current password does not match.</value>
</data>
<data name="security.change.password.not.changed" xml:space="preserve">
<value>Your new password must be different from the previous.</value>
</data>
<data name="security.msg.change.password.failed" xml:space="preserve">
<value>Failed to change password.</value>
</data>
<data name="security.msg.request.password.email.sent" xml:space="preserve">
<value>If your email address matches an active user account, you will shortly receive an email with instructions for changing your password.</value>
</data>
<data name="security.msg.request.password.saved" xml:space="preserve">
<value>Password has been updated</value>
</data>
<data name="security.msg.signin.invalid" xml:space="preserve">
<value>Incorrect account credentials entered.</value>
</data>
<data name="security.msg.signout.confirm" xml:space="preserve">
<value>Are you sure that you want to sign-out?</value>
</data>
<data name="security.msg.warn.sessionExpired" xml:space="preserve">
<value>Your session has expired.</value>
</data>
<data name="support.caption.user.report" xml:space="preserve">
<value>User Report</value>
</data>
<data name="support.caption.user.reports" xml:space="preserve">
<value>User Reports</value>
</data>
</root>
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Saxonite.Net.Core.Web.Online.App
{
//=======================================================================
public class Program
{
//-------------------------------------------------------------------
public static void Main(string[] args)
{
try
{
CreateHostBuilder(args).Build().Run();
}
finally
{
// Cleanup - globals, async processes etc.
if (SaxApplication_Online.AppInstance != null)
SaxApplication_Online.AppInstance.Dispose();
}
}
//-------------------------------------------------------------------
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<SaxStartup_Online>();
});
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,145 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Sarand.Net.Standard.Dbas.Base;
using Sarand.Net.Standard.Dbas.Data;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Email;
using Sarand.Net.Standard.Base.IO;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Core.Web.Online.Consoles;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Dbas.App;
using Microsoft.Extensions.Hosting;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System.Text.RegularExpressions;
namespace Saxonite.Net.Core.Web.Online.App
{
//=======================================================================
public class SaxApplication_Online : SaxApplication_Web
{
//-------------------------------------------------------------------
// Data members.
private Dictionary<String, SaxAppConfig> appConfigs = new Dictionary<string, SaxAppConfig>();
private Dictionary<String, SaxEmailConfig> emailConfigs = new Dictionary<string, SaxEmailConfig>();
// Session contexts.
protected class SessionContextData_Online : SessionContextData_Web
{
public SessionContextData_Online(SaxSessionContext_Web sessionContext) : base(sessionContext) { }
}
private readonly Dictionary<int, SessionContextData_Online> sessionContexts = new Dictionary<int, SessionContextData_Online>();
//-------------------------------------------------------------------
// Construction.
public SaxApplication_Online(IConfiguration configuration) : base(Sax.Application.Online)
{
InitialiseTemporaryLoggingFolder();
try
{
AppInstance = this;
Configuration = configuration;
ConsoleHelper_Online = new SaxConsoleHelper_Online(this);
}
catch (Exception exc)
{
SaLogger.Error($"{GetType().Name}.CTR {ExceptionUtil.GetErrorStateMessage(exc)}");
throw;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Clear globals.
AppInstance = null;
// Console helper.
if (ConsoleHelper_Online != null)
ConsoleHelper_Online.Dispose();
ConsoleHelper_Online = null;
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Globals.
public static SaxApplication_Online AppInstance { get; private set; }
public override SaxConsoleHelper ConsoleHelper { get { return ConsoleHelper_Online; } }
public SaxConsoleHelper_Online ConsoleHelper_Online { get; private set; }
// Database.
public override ISacsDatabase GetDatabase(SaxSessionContext sessionContext, Sax.Database databaseType)
{
return sessionContexts.GetValue(sessionContext.Index, null)?.Databases.GetValue(databaseType, null) ?? null;
}
// Configuration.
public override SaxAppConfig AppConfig { get { return appConfigs.Values.ToList().getConfigForApplication(); } }
public override SaxEmailConfig EmailConfig { get { return emailConfigs.Values.ToList().getConfigForSession(AppConfig); } }
public override String SessionCookieName { get { return Sax.Config.Online.Session.Cookie.Name; } }
//-------------------------------------------------------------------
// Initialisation.
/// <summary>Initialise environment - from Startup.</summary>
internal void InitWebHostEnvironment(IWebHostEnvironment env)
{
InitEnvironment(env);
}
/// <summary>Initialise environment - from internal (above) or unit tests.</summary>
protected void InitEnvironment(IWebHostEnvironment env)
{
base.InitEnvironment(env, "Sax-Web-Onl");
try
{
// Application configuration.
appConfigs = SaxAppConfigHelper.ExtractAppConfigs(SaxConfiguration.GetSection("AppConfigs"));
emailConfigs = SaxEmailConfigHelper.ExtractEmailConfigs(SaxConfiguration.GetSection("EmailConfigs"));
// Ensure root log folder ends with 'Online' folder.
LoggingRootFolder = AppConfig.Logging.RootFolder;
if (!StringUtil.IsBlank(LoggingRootFolder) && !Regex.IsMatch(LoggingRootFolder, "[\\\\/]Online$", RegexOptions.IgnoreCase))
LoggingRootFolder = StringUtil.CombineFilePath(LoggingRootFolder, "Online");
// Application instance.
if (AppConfig.Instance.EqualsCI("Dev")) instanceType = Sax.InstanceType.Dev;
else if (AppConfig.Instance.ContainsCI("Q")) instanceType = Sax.InstanceType.QC;
else if (AppConfig.Instance.ContainsCI("UAT")) instanceType = Sax.InstanceType.UAT;
else if (AppConfig.Instance.EqualsCI("PreProd")) instanceType = Sax.InstanceType.UAT;
// Initialise hard-coded logging path.
if (StringUtil.IsEmpty(LoggingRootFolder))
{
if (SaxSystem.ComputerName.EqualsCI("WIN-BPCLM1QUOMT"))
LoggingRootFolder = @"D:\Saxonite\Logs\" + (Development ? "Dev" : "Test");
}
if (DirectoryUtil.DirectoryExists(LoggingRootFolder))
SaLogger.LogFolder = LoggingRootFolder;
LogApplicationStart(SysUtil.Fn);
// Session context - only one context available/necessary (for now).
SessionContextData_Online sessionContextData = new SessionContextData_Online(new SaxSessionContext_Web(100, ApplicationType));
ConfigureDatabaseConnection(sessionContextData, Sax.Database.Core);
ConfigureDatabaseConnection(sessionContextData, Sax.Database.Reference);
ConfigureDatabaseConnection(sessionContextData, Sax.Database.Support);
sessionContexts.Add(sessionContextData.SessionContext.Index, sessionContextData);
SessionContext = sessionContextData.SessionContext;
// Data Cache console - to initialise refresh. Must be done for every Session Context.
SaxDataCacheConsole dataCacheConsole = SaxGlobals.ConsoleHelper.GetDataCacheConsole(SessionContext);
}
catch (Exception exc)
{
SaLogger.Error(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc));
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using Saxonite.Net.Core.Web.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Resources;
using System.Web;
namespace Saxonite.Net.Core.Web
{
// <summary>
/// Dummy classes to group shared resources - named as per '.resx' file in root namespace.
/// <para>Required to create the IStringLocalizer instance using the type from the class.</para>
/// <para>Name of the class is used as the prefix for localisation filenames.</para>
/// <para>Resource files are in the Localization ResourcesPath in Startup.cs.</para>
/// </summary>
public class SaxOnlineProperties { }
// Class must be in Root/Default Namespace to reference properties with same name.
// NB 'Assembly name' must be the same as 'Default Namespace' until 'Root Namespace' can be set (.Net Core 2.1?).
// Ref. Project Properties - Application.
}
namespace Saxonite.Net.Core.Web.Online.App
{
//=======================================================================
public class SaxLocaliser_Online : SaxLocaliser
{
//-------------------------------------------------------------------
// Data members.
private readonly IStringLocalizer<SaxOnlineProperties> webAppProperties;
//-------------------------------------------------------------------
/// <summary>
/// Localisers are supplied using construction injection.
/// </summary>
public SaxLocaliser_Online(IStringLocalizer<SaxOnlineProperties> webAppProperties)
{
this.webAppProperties = webAppProperties;
}
//-------------------------------------------------------------------
// Properties.
public override IStringLocalizer MainProperties { get { return webAppProperties; } }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,189 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.Controllers;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.App
{
//=======================================================================
public class SaxSessionData_Online : SaxSessionData_Web
{
//-------------------------------------------------------------------
// Construction.
[JsonConstructor]
private SaxSessionData_Online(long sessionIdx, int itemIdx, SaxSessionContext sessionContext) :
base(sessionIdx, itemIdx, sessionContext)
{ }
/// <summary>
/// SessionData construction.
/// <para>SessionContext and UserAccount must be supplied.</para>
/// <para>Organisation is optional.</para>
/// </summary>
public SaxSessionData_Online(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles) :
base(sessionContext, userAccount, userRoles)
{
}
//-------------------------------------------------------------------
// Contact details.
private class UserData_Online : UserData
{
public UserData_Online(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles) :
base(sessionContext, userAccount, userRoles)
{ }
public UserData_Online(UserData userData) :
base(userData)
{ }
public new bool RefreshUserData(SaxUserAccount userAccount)
{
return base.RefreshUserData(userAccount);
}
}
protected override UserData CreateUserData(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles) { return new UserData_Online(sessionContext, userAccount, userRoles); }
/// <summary>User - JSON deserialisation route.</summary>
public new UserData User { get { return base.User; } set { SetUser(new UserData_Online(value)); } }
/// <summary>
/// Update the user's contect details against the session.
/// <para>- User Account must match that already against the session.</para>
/// </summary>
public bool RefreshUser(SaxUserAccount userAccount)
{
UserData_Online user = User as UserData_Online;
if (user != null)
return user.RefreshUserData(userAccount);
return false;
}
//-------------------------------------------------------------------
// User Config - user's application settings.
public class UserConfigData
{
/// <summary>Online Profile Config data.</summary>
public SaxConfigData.ProfileData Profile{ get; set; } = new SaxConfigData.ProfileData();
}
/// <summary>User Configuration.</summary>
public UserConfigData UserConfig { get; set; } = new UserConfigData();
//-------------------------------------------------------------------
// User Profile.
// Protected constructors & setters for JSON deserialisation.
public class UserProfileData
{
public UserProfileData() { }
// Sports.
public class SportData
{
[JsonConstructor]
public SportData(SysId sportId, SaStatus status)
{
SportId = sportId;
Status = status;
}
public SysId SportId { get; protected set; }
public SaStatus Status { get; protected set; }
}
private class SportData_Online : SportData
{
// Internal class to allow values to be updated.
public SportData_Online(SysId sportId, SaStatus status) : base(sportId, status) { }
public SportData_Online(SportData sportData) : base(sportData.SportId, sportData.Status) { }
public void SetSportId(SysId sportId) { SportId = sportId; }
public void SetStatus(SaStatus status) { Status = status; }
}
[JsonIgnore]
private readonly List<SportData_Online> sports = new List<SportData_Online>();
[JsonIgnore]
public int SportCount { get { return sports.Count; } }
public List<SportData> Sports { get { return new List<SportData>(sports); } protected set { sports.Clear(); sports.AddRange(value.Select(item => new SportData_Online(item))); } }
public void AddSport(SysId sportId, SaStatus status)
{
if (!SysId.IsValid(sportId)) throw new NotSupportedException("Session Profile Sport must have a valid Id");
SportData_Online sport = this.sports.Find(item => item.SportId == sportId);
if (sport != null)
{
sport.SetStatus(status);
}
else
this.sports.Add(new SportData_Online(sportId, status));
}
public void ClearSports()
{
this.sports.Clear();
}
}
public UserProfileData Profile { get; protected set; } = new UserProfileData();
//-------------------------------------------------------------------
// Filter properties.
public class FilterData
{
/// <summary>Id for currently selected Organisation.</summary>
public SysId OrganisationId { get; set; }
/// <summary>Display text for currently selected Organisation.</summary>
public String OrganisationCaption { get; set; }
/// <summary>
/// Store the properties for the currently selected Organisation.
/// </summary>
public bool SetOrganisation(SaxOrganisation organisation)
{
if (organisation == null) return false;
if (!organisation.IsStatusActive) return false;
OrganisationId = organisation.Id;
OrganisationCaption = organisation.Name;
return true;
}
/// <summary>Id for currently selected Organisation of which the user is a member.</summary>
public SysId OrganisationIdMember { get; set; }
/// <summary>Display text for currently selected Organisation of which the user is a member.</summary>
public String OrganisationCaptionMember { get; set; }
/// <summary>
/// Store the properties for the currently selected Organisation for which the User is a member.
/// </summary>
public bool SetMemberOrganisation(SaxOrganisation organisation)
{
if (organisation == null) return false;
if (!organisation.IsStatusActive) return false;
OrganisationIdMember = organisation.Id;
OrganisationCaptionMember = organisation.Name;
return true;
}
}
public FilterData Filter { get; private set; } = new FilterData();
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// JSON Deserialisation.
[JsonExtensionData]
#pragma warning disable IDE0044 // Add readonly modifier
private IDictionary<string, JToken> _additionalData = null;
#pragma warning restore IDE0044 // Add readonly modifier
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_additionalData != null)
{
foreach (String name in _additionalData.Keys)
{
JToken value = _additionalData[name];
SaLogger.Info("OnDeserialized Name: " + name + ". Value: " + value.ToString());
}
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Core.Web.Base.Middleware;
using Microsoft.AspNetCore.Internal;
using Saxonite.Net.Core.Web.Base.App;
using Microsoft.Extensions.Hosting;
using Sarand.Net.Standard.Base.Core;
namespace Saxonite.Net.Core.Web.Online.App
{
//=======================================================================
public class SaxStartup_Online : SaxStartup
{
//-------------------------------------------------------------------
// Data members.
private SaxApplication_Online application = null;
//-------------------------------------------------------------------
// Construction.
public SaxStartup_Online(IConfiguration configuration) : base(configuration)
{
// Initialise application.
application = new SaxApplication_Online(Configuration);
}
//-------------------------------------------------------------------
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Common configuration.
ConfigureServicesConfig configureServicesConfig = new ConfigureServicesConfig();
configureServicesConfig.services = services;
configureServicesConfig.sessionCookieName = Sax.Config.Online.Session.Cookie.Name;
configureServicesConfig.sessionCookieHttpOnly = Sax.Config.Online.Session.Cookie.HttpOnly;
configureServicesConfig.sessionIdleTimeout = Sax.Config.Online.Session.Cookie.IdleTimeout;
ConfigureServices(configureServicesConfig);
// Application localisation.
services.AddSingleton<SaxLocaliser, SaxLocaliser_Online>();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
}
//-------------------------------------------------------------------
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostApplicationLifetime hostApplicationLifetime, IWebHostEnvironment env)
{
application.InitWebHostEnvironment(env);
// Common configuration.
ConfigureConfig configureConfig = new ConfigureConfig();
configureConfig.app = app;
configureConfig.hostApplicationLifetime = hostApplicationLifetime;
configureConfig.env = env;
Configure(configureConfig);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,31 @@
using Sarand.Net.Standard.Base.App;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Online.Consoles
{
//=======================================================================
public class SaxActivityConsole_Online : SaxActivityConsole_Web
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
public SaxActivityConsole_Online(SaxSessionContext sessionContext) :
base(sessionContext)
{
String fn = GetType().Name + ".CTR";
SaLogger.Info(fn + "");
}
//-------------------------------------------------------------------
// Utilities.
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,88 @@
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Consoles
{
//=======================================================================
public class SaxConsoleHelper_Online : SaxConsoleHelper_Web
{
//-------------------------------------------------------------------
// Data members.
private readonly SaxApplication_Online app;
//-------------------------------------------------------------------
// construction.
public SaxConsoleHelper_Online(SaxApplication_Online app)
{
this.app = app;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Data cache consoles.
foreach (SaxDataCacheConsole_Online dataCacheConsole in dataCacheConsoles.Values)
dataCacheConsole.Dispose();
dataCacheConsoles.Clear();
// Session data consoles.
foreach (SaxSessionDataConsole_Online sessionDataConsole in sessionDataConsoles.Values)
sessionDataConsole.Dispose();
sessionDataConsoles.Clear();
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Data cache console.
private readonly Dictionary<ushort, SaxDataCacheConsole_Online> dataCacheConsoles = new Dictionary<ushort, SaxDataCacheConsole_Online>();
public override SaxDataCacheConsole GetDataCacheConsole(SaxSessionContext sessionContext)
{
if (sessionContext == null) throw new NotSupportedException($"{SysUtil.Fn} Session Context must be specified.");
SaxDataCacheConsole_Online dataCacheConsole = dataCacheConsoles.GetValueOrDefault(sessionContext.Index, null);
if (dataCacheConsole == null)
{
dataCacheConsole = dataCacheConsoles[sessionContext.Index] = new SaxDataCacheConsole_Online(sessionContext);
}
return dataCacheConsole;
}
//-------------------------------------------------------------------
// Session Data console.
private readonly Dictionary<ushort, SaxSessionDataConsole_Online> sessionDataConsoles = new Dictionary<ushort, SaxSessionDataConsole_Online>();
public override SaxSessionDataConsole GetSessionDataConsole(SaxSessionContext sessionContext)
{
if (sessionContext == null) throw new NotSupportedException($"{SysUtil.Fn} Session Context must be specified.");
SaxSessionDataConsole_Online sessionDataConsole = sessionDataConsoles.GetValueOrDefault(sessionContext.Index, null);
if (sessionDataConsole == null)
{
sessionDataConsole = sessionDataConsoles[sessionContext.Index] = new SaxSessionDataConsole_Online(sessionContext);
}
return sessionDataConsole;
}
//-------------------------------------------------------------------
// Activity console.
public override SaxActivityConsole CreateActivityConsole(SaxSessionContext sessionContext)
{
return new SaxActivityConsole_Online(sessionContext);
}
//-------------------------------------------------------------------
// Email console.
public override SaxEmailConsole CreateEmailConsole(SaxSessionContext sessionContext)
{
SaxEmailConsole emailConsole = new SaxEmailConsole_Web(app.EmailConfig);
return emailConsole;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,63 @@
using Sarand.Net.Standard.Base.App;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Text;
namespace Saxonite.Net.Core.Web.Online.Consoles
{
//=======================================================================
public class SaxDataCacheConsole_Online : SaxDataCacheConsole_Web
{
//-------------------------------------------------------------------
private System.Timers.Timer refreshTimer;
//-------------------------------------------------------------------
// Construction.
public SaxDataCacheConsole_Online(SaxSessionContext sessionContext) :
base(sessionContext)
{
String fn = GetType().Name + ".CTR";
RegionsSupported = true;
SportsSupported = true;
// Initialise refresh timer.
refreshTimer = new System.Timers.Timer(Math.Max(2, Sax.Config.Online.DataCache.RefreshIntervalSecs.Initial) * 1000);
refreshTimer.Elapsed += RefreshTimer_Elapsed;
refreshTimer.Start();
SaLogger.Info(fn + " Refresh timer initialised. Interval: " + refreshTimer.Interval, true);
}
//-------------------------------------------------------------------
// Utilities.
protected override void ForceRefresh()
{
String fn = GetType().Name + ".ForceRefresh";
SaLogger.Warn(fn + " Refresh timer initialised", true);
RefreshData();
}
//-------------------------------------------------------------------
// Refresh data cache.
private void RefreshTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
refreshTimer.Stop();
RefreshData();
}
private void RefreshData()
{
refreshTimer.Stop();
if (RegionsSupported) RefreshRegions();
if (SportsSupported) RefreshSports();
refreshTimer.Interval = Math.Max(10, Sax.Config.Online.DataCache.RefreshIntervalSecs.Periodic) * 1000;
refreshTimer.Start();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,119 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.Consoles;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Saxonite.Net.Core.Web.Online.Consoles
{
//=======================================================================
public class SaxSessionDataConsole_Online : SaxSessionDataConsole_Web
{
//-------------------------------------------------------------------
// Definitions.
//-------------------------------------------------------------------
// Data members.
private static SaxSessionDataConsole_Online console = null;
private System.Timers.Timer commitTimer = null;
/// <summary>Latest time that a user was active on the online system - awaiting commit to the database.</summary>
private SacsQueueDictionary<SysId, SaxUserOnline.DataProperties> usersActiveAt = new SacsQueueDictionary<SysId, SaxUserOnline.DataProperties>();
private DateTime lastDataCommit = DateTime.MinValue;
private int commitBatchSize = 32;
//-------------------------------------------------------------------
// Construction.
public SaxSessionDataConsole_Online(SaxSessionContext sessionContext) :
base(sessionContext)
{
String fn = GetType().Name + ".CTR";
if (console != null)
{
SaLogger.Info(fn + " Instance already exists");
throw new InvalidOperationException(GetType().Name + " - instance already exists.");
}
console = this;
SaLogger.Info(fn + "");
// Initilise commit timer.
commitTimer = new System.Timers.Timer(Math.Max(2, Sax.Config.Online.DataCache.RefreshIntervalSecs.Initial) * 1000);
commitTimer.Elapsed += CommitTimer_Elapsed;
commitTimer.Start();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Object.ReferenceEquals(console, this))
console = null;
}
base.Dispose(disposing);
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
// Utilities.
//-------------------------------------------------------------------
// Record User Online activity.
//-------------------------------------------------------------------
public override bool RecordUserActive(SysId userAccountId, DateTime activeAt)
{
if (!SysId.IsValid(userAccountId) || !DateUtil.IsValid(activeAt)) return false;
usersActiveAt.SetEntry(userAccountId, new SaxUserOnline.DataProperties() { UserAccountId = userAccountId, OnlineAt = activeAt });
return true;
}
//-------------------------------------------------------------------
// Commit User Online activity.
//-------------------------------------------------------------------
public override DateTime LastDataCommit()
{
lock (usersActiveAt)
return lastDataCommit;
}
private void CommitTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
commitTimer.Stop();
CommitData();
}
private void CommitData()
{
String fn = GetType().Name + ".CommitData";
commitTimer.Stop();
// Extract data to be committed.
List<KeyValuePair<SysId, SaxUserOnline.DataProperties>> usersActiveAtToCommit = usersActiveAt.RemoveTopEntries(commitBatchSize);
// Reset the timer.
int intervalSecs = Sax.Config.Online.Session.DataCommit.IntervalSecs.NoDataWaiting;
if (usersActiveAt.Count > 0)
{
// Additional Data is waiting to be committed.
intervalSecs = Sax.Config.Online.Session.DataCommit.IntervalSecs.DataWaiting;
}
commitTimer.Interval = Math.Max(1, intervalSecs) * 1000;
commitTimer.Start();
// Commit the data.
if (usersActiveAtToCommit.Count > 0)
{
if (SacsSystem.DebugMode)
SaLogger.Info(fn + " Entries to commit: " + usersActiveAtToCommit.Count);
Internals.AccountMgr.CommitUsersOnline(usersActiveAtToCommit.Select(ItemId => ItemId.Value).ToList());
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,426 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Standard.Base.App;
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.Helpers;
using System.IO;
using System.Drawing;
using Sarand.Net.Standard.Base.Core;
using Saxonite.Net.Standard.Base.Data;
using static Saxonite.Net.Standard.Base.App.Sax.Config;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller - Contact - Ajax actions.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
// Common response data.
private class ContactResponseData
{
public String EmailAddress { get; set; }
public String DisplayName { get; set; }
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String About { get; set; }
public byte Gender { get; set; }
public String GenderCaption { get; set; }
public byte DobDay { get; set; }
public byte DobMonth { get; set; }
public short DobYear { get; set; }
public bool? HasImage { get; set; } = null;
// Regions.
public String RegionIdCountry { get; set; }
public String RegionCaptionCountry { get; set; }
public String RegionIdProvince { get; set; }
public String RegionCaptionProvince { get; set; }
public String LocationCaption { get; set; }
}
private void PopulateContactResponseData(SaxModel_Online model, ContactResponseData data, SaxUserAccount userAccount)
{
data.EmailAddress = userAccount.EmailAddress;
data.DisplayName = userAccount.GetDisplayName();
data.Title = userAccount.Title;
data.FirstName = userAccount.FirstName;
data.LastName = userAccount.LastName;
data.About = userAccount.About;
data.Gender = (byte)userAccount.Gender;
data.GenderCaption = Localiser.GetGenderCaption(userAccount.Gender);
data.DobDay = (byte)(userAccount.DateOfBirthDay ?? 0);
data.DobMonth = (byte)(userAccount.DateOfBirthMonth ?? 0);
data.DobYear = (short)(userAccount.DateOfBirthYear ?? 0);
if (ItemDataMgr.ItemHasActiveImage(userAccount.GetItemId(), Sax.ImageType.Avatar))
data.HasImage = true;
// Regions.
data.RegionIdCountry = model.GetEncryptedRegionId(userAccount.RegionIdCountry);
data.RegionIdProvince = model.GetEncryptedRegionId(userAccount.RegionIdProvince);
data.RegionCaptionCountry = Localiser.GetRegionName(Internals.GetRegion(userAccount.RegionIdCountry));
data.RegionCaptionProvince = Localiser.GetRegionName(Internals.GetRegion(userAccount.RegionIdProvince));
List<String> locations = new List<string>() { data.RegionCaptionProvince, data.RegionCaptionCountry };
data.LocationCaption = locations.Concat(", ", "", "", true, true);
}
//-------------------------------------------------------------------
// Utilites.
private ItemId GetUserItemId(SysId userAccountId)
{
return new ItemId((short)Sax.DataItemType.UserAccount, userAccountId);
}
//-------------------------------------------------------------------
// Account - Get Contact Details.
//-------------------------------------------------------------------
private class AjaxResponse_AccountContactData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_AccountContactData(SaxModel_Online model) : base(model) { }
// Contact details.
public ContactResponseData Contact { get; set; }
// Reference data.
public List<CodeDesc> Genders { get; } = new List<CodeDesc>();
public List<CodeDesc> Months { get; } = new List<CodeDesc>();
public List<String> ImageContentTypes { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/GetProfileContact")]
public IActionResult GetProfileContact(Model_Account model)
{
String fn = GetType().Name + ".GetProfileContact";
AjaxResponse_AccountContactData responseData = new AjaxResponse_AccountContactData(model);
String messageTitle = Localiser["account.caption.profile.contact"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Reference data - Genders.
responseData.Genders.Add("", Localiser["label.not.specified"]);
foreach (TypeCodeDesc gender in Internals.GenderCodes)
responseData.Genders.Add(gender.Type.ToString(), Localiser.GetGenderCaption((Sax.Gender)gender.Type));
// Reference data - Months.
for (int month = 1; month <= 12; month++)
responseData.Months.Add(month.ToString(), DateUtil.Format(new DateTime(DateTime.Today.Year, month, 1), "MMMM", SessionCulture));
// Reference data - Image content types.
responseData.ImageContentTypes = SaxItemImage.GetValidContentTypes(Sax.DataItemType.UserAccount);
// Retrieve user details.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionUserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve user account for Id: " + SysId.ToString(SessionUserAccountId));
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
}
// Logically delete any previous 'Uploaded' Avatar image.
ItemId userItemId = userAccount.GetItemId();
SaxItemImage itemImageAvatarUploaded = ItemDataMgr.GetItemImage(userItemId, Sax.ImageType.Avatar_Upload, SaxItemImage.StatusCat.ActiveOnly);
disposableItems.Add(itemImageAvatarUploaded);
if (itemImageAvatarUploaded != null)
{
itemImageAvatarUploaded.Status = SaStatus.Deleted;
if (!ItemDataMgr.UpdateItemImage(itemImageAvatarUploaded, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to logically delete uploaded avatar image - " + ItemDataMgr.ErrorStateMessage);
}
// Populate data to response.
responseData.Contact = new ContactResponseData();
PopulateContactResponseData(model, responseData.Contact, userAccount);
}
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Upload Contact Image.
// Ref. http://www.binaryintellect.net/articles/474dc031-087f-4b81-b994-9ca2adb949d6.aspx
//-------------------------------------------------------------------
private class AjaxResponse_UploadContactImage : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UploadContactImage(SaxModel_Online model) : base(model) { }
// Upload message.
public String UploadMsg { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/UploadContactImage")]
public IActionResult UploadContactImage(Model_Account model)
{
String fn = GetType().Name + ".UploadContactImage";
AjaxResponse_UploadContactImage responseData = new AjaxResponse_UploadContactImage(model);
String messageTitle = Localiser["account.caption.profile.contact"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate number of images.
if (model.AccountContactImage.IsEmpty())
{
SaLogger.Warn(fn + " No image file supplied for upload");
return Json(responseData);
}
if (model.AccountContactImage.Count != 1)
{
SaLogger.Warn(fn + " Invalid number of image files uploaded: " + model.AccountContactImage.Count);
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.single"]));
}
IFormFile file = model.AccountContactImage[0];
// Image properties.
byte[] imageData = null;
using (MemoryStream fileStream = new MemoryStream())
{
file.CopyTo(fileStream);
imageData = fileStream.ToArray();
}
// Validate content type.
if (StringUtil.IsEmpty(file.ContentType) || !SaxItemImage.GetValidContentTypes(Sax.DataItemType.UserAccount).ContainsCI(file.ContentType))
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.invalid.content.type"]));
Sarand.Net.Standard.Base.Core.Sacs.MimeType mimeTypeExpected = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType);
// Validate the image.
ImageUtils.ImageConfig imageConfig = null;
if (mimeTypeExpected == Sarand.Net.Standard.Base.Core.Sacs.MimeType.Svg)
imageConfig = ImageUtils.VerifySvgImageData(imageData);
else
imageConfig = ImageUtils.VerifyImageData(imageData);
if (!imageConfig.ValidImage)
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.invalid"]));
// Validate mime type.
if (!SaxItemImage.GetValidMimeTypes(Sax.DataItemType.UserAccount).Contains(imageConfig.MimeType))
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.invalid.mime"]));
if (imageConfig.MimeType != mimeTypeExpected)
{
SaLogger.Warn($"{SysUtil.Fn} Image upload - inconsistent Mime. Filename: {file.FileName}. Mime: {imageConfig.MimeType}. Expected Mime: {mimeTypeExpected}.");
return Json(responseData.SetControlError("OrganisationLogoImage", Localiser["msg.upload.image.invalid"]));
}
// Resize the image if required.
if (!ImageUtils.ConstrainImage(imageConfig, Sax.Config.User.Image.WidthMax, Sax.Config.User.Image.HeightMax))
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.resize.failed"]));
// Image belongs to the user's account.
ItemId userItemId = GetUserItemId(SessionUserAccountId);
// Store uploaded image as 'Upload' Avatar - awaiting confirmation that the contact details are to be saved.
SaxItemImage itemImage = ItemDataMgr.GetItemImage(userItemId, Sax.ImageType.Avatar_Upload, SaxItemImage.StatusCat.Any);
if (itemImage == null)
itemImage = ItemDataMgr.CreateItemImage(userItemId, Sax.ImageType.Avatar_Upload);
disposableItems.Add(itemImage);
itemImage.Filename = file.FileName;
itemImage.Mime = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType).ToString().ToLower();
itemImage.ContentType = file.ContentType;
itemImage.ImageData = imageConfig.ImageData;
itemImage.Width = imageConfig.Width;
itemImage.Height = imageConfig.Height;
itemImage.Status = SaxItemImage.DefaultStatus;
if (!SysId.IsValid(itemImage.Id))
{
if (!ItemDataMgr.InsertItemImage(itemImage, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert uploaded image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update uploaded image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.error.item.insert.failed"]));
}
}
// Complete response.
//responseData.SetNotifyMessage(messageTitle, "Image uploaded.");
}
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Store Contact Details.
//-------------------------------------------------------------------
private class AjaxResponse_StoreAccountContactDetails : AjaxResponse_Data
{
// Construction.
public AjaxResponse_StoreAccountContactDetails(SaxModel_Online model) : base(model) { }
// Contact details.
public ContactResponseData Contact { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/StoreProfileContact")]
public IActionResult StoreProfileContact(Model_Account model)
{
String fn = GetType().Name + ".StoreProfileContact";
AjaxResponse_StoreAccountContactDetails responseData = new AjaxResponse_StoreAccountContactDetails(model);
String messageTitle = Localiser["account.caption.profile.contact"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate data.
model.ValidateContactDetails(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve user details.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionUserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve user account for Id: " + SysId.ToString(SessionUserAccountId));
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
}
if (!userAccount.EmailAddress.EqualsCI(SessionData.User.EmailAddress))
{
SaLogger.Warn(fn + " Unable to update user account '" + userAccount.EmailAddress + ". Email doesn't match session: " + SessionData.User.EmailAddress);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
}
// Populate user properties.
userAccount.Changed = false;
if (model.AccountTitle != null)
userAccount.Title = model.AccountTitle;
if (model.AccountFirstName != null)
userAccount.FirstName = model.AccountFirstName;
if (model.AccountLastName != null)
userAccount.LastName = model.AccountLastName;
if (model.AccountAbout != null)
userAccount.About = model.AccountAbout;
if (model.AccountGender != null)
userAccount.Gender = model.GetAccountGender();
// Date of birth.
if (model.AccountDobYear != null)
userAccount.DateOfBirthYear = StringUtil.ToShort(model.AccountDobYear);
if (userAccount.DateOfBirthYear > 0)
{
if (model.AccountDobMonth != null)
userAccount.DateOfBirthMonth = (byte)StringUtil.ToShort(model.AccountDobMonth);
if (userAccount.DateOfBirthMonth > 0)
{
if (model.AccountDobDay != null)
userAccount.DateOfBirthDay = (byte)StringUtil.ToShort(model.AccountDobDay);
}
else userAccount.DateOfBirthDay = 0;
}
else
{
userAccount.DateOfBirthMonth = 0;
userAccount.DateOfBirthDay = 0;
}
// Regions.
if (model.AccountRegionIdCountry != null)
userAccount.RegionIdCountry = model.ExtractSysId(model.AccountRegionIdCountry);
if (model.AccountRegionIdProvince != null)
userAccount.RegionIdProvince = model.ExtractSysId(model.AccountRegionIdProvince);
// Update User Account.
if (userAccount.Changed) SaLogger.Info(fn + " Updating user account '" + userAccount.EmailAddress + "'. Id: " + SysId.ToString(userAccount.Id), true);
if (AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
// ToDo Refresh Session with updated organisation details.
}
else
{
SaLogger.Warn(fn + " Failed to update user account '" + userAccount.EmailAddress + "': " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
// Refresh profile contact details to session.
RefreshSessionProfileContact(userAccount);
// Check for uploaded profile image.
ItemId userItemId = userAccount.GetItemId();
SaxItemImage itemImageAvatarUploaded = ItemDataMgr.GetItemImage(userItemId, Sax.ImageType.Avatar_Upload, SaxItemImage.StatusCat.ActiveOnly);
disposableItems.Add(itemImageAvatarUploaded);
if (itemImageAvatarUploaded != null)
{
// Retrieve current Avatar image.
SaxItemImage itemImageAvatar = ItemDataMgr.GetItemImage(userItemId, Sax.ImageType.Avatar, SaxItemImage.StatusCat.Any);
if (itemImageAvatar == null)
itemImageAvatar = ItemDataMgr.CreateItemImage(userItemId, Sax.ImageType.Avatar);
disposableItems.Add(itemImageAvatar);
// Copy properties from uploaded image.
itemImageAvatar.Filename = itemImageAvatarUploaded.Filename;
itemImageAvatar.Mime = itemImageAvatarUploaded.Mime;
itemImageAvatar.ContentType = itemImageAvatarUploaded.ContentType;
itemImageAvatar.Width = itemImageAvatarUploaded.Width;
itemImageAvatar.Height = itemImageAvatarUploaded.Height;
itemImageAvatar.ImageData = itemImageAvatarUploaded.ImageData;
itemImageAvatar.Status = SaxItemImage.DefaultStatus;
if (!SysId.IsValid(itemImageAvatar.Id))
{
if (!ItemDataMgr.InsertItemImage(itemImageAvatar, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert avatar image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!ItemDataMgr.UpdateItemImage(itemImageAvatar, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update avatar image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.error.item.update.failed"]));
}
}
// Logically delete the uplaoded image.
itemImageAvatarUploaded.Status = SaStatus.Deleted;
if (!ItemDataMgr.UpdateItemImage(itemImageAvatarUploaded, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to logically delete uploaded avatar image - " + ItemDataMgr.ErrorStateMessage);
}
// Construct response.
responseData.Contact = new ContactResponseData();
PopulateContactResponseData(model, responseData.Contact, userAccount);
}
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,523 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
using MailKit;
using Org.BouncyCastle.Asn1.Mozilla;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller - User Eligibility - Ajax actions.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
// Data definitions.
private class UserEligibilityResponseListData : AjaxResponse_Data.ItemData_Base
{
public String UserAccountId { get; set; }
public String RegionName { get; set; }
public String EligibilityTypeDesc { get; set; }
public String Info { get; set; }
public String ExpiresOnFmt { get; set; }
}
private class UserEligibilityResponseItemData : UserEligibilityResponseListData
{
public String RegionId { get; set; }
public String EligibilityType { get; set; }
public String ExpiresOnFrm { get; set; }
}
private void PopulateUserEligibilityResponseListData(SaxModel_Online model, SaxUserEligibility userEligibility, UserEligibilityResponseListData data)
{
data.Id = model.EncryptId(userEligibility.Id);
data.UserAccountId = model.EncryptId(userEligibility.UserAccountId);
data.RegionName = model.GetRegionCaption(userEligibility.RegionId);
data.EligibilityTypeDesc = Localiser.GetEligibilityTypeCaption(userEligibility.EligibilityType);
data.Info = userEligibility.Info;
data.ExpiresOnFmt = model.FormatDateForDisplay(userEligibility.ExpiresOn);
}
private void PopulateUserEligibilityResponseItemData(SaxModel_Online model, SaxUserEligibility userEligibility, UserEligibilityResponseItemData data)
{
PopulateUserEligibilityResponseListData(model, userEligibility, data);
data.RegionId = model.GetEncryptedRegionId(userEligibility.RegionId);
data.EligibilityType = EligibilityTypeCodes.GetCodeForType(userEligibility.EligibilityType);
data.ExpiresOnFrm = model.FormatDateForForm(userEligibility.ExpiresOn);
data.CreatedOnFmt = model.FormatDateForDisplay(userEligibility.CreateStamp.On);
data.StatusCode = userEligibility.StatusCode;
data.StatusDesc = userEligibility.StatusDesc;
}
//-------------------------------------------------------------------
// Account - Get User Eligibility list.
//-------------------------------------------------------------------
private class EligibilityTypeData
{
public String Code { get; set; }
public String Desc { get; set; }
/// <summary>Does the eligibility type allow a date range.</summary>
public bool? Dates { get; set; } = null;
}
private class AjaxResponse_UserEligibilityListData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserEligibilityListData(SaxModel_Online model) : base(model) { }
// Eligibility list.
public List<UserEligibilityResponseListData> Eligibilities { get; } = new List<UserEligibilityResponseListData>();
// Eligibility types.
public List<EligibilityTypeData> Types { get; } = new List<EligibilityTypeData>();
// User configuration.
public class ConfigData
{
public bool? hideInfo { get; set; } = null;
}
public ConfigData Config { get; } = new ConfigData();
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/GetUserEligibilities")]
[Route("Account/Ajax/GetUserEligibilityList")]
public IActionResult GetUserEligibilities(Model_UserEligibility model)
{
String fn = GetType().Name + ".GetUserEligibilities";
AjaxResponse_UserEligibilityListData responseData = new AjaxResponse_UserEligibilityListData(model);
String messageTitle = Localiser["account.caption.profile.eligibility"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Eligibility types.
foreach (Sax.EligibilityType eligibilityType in EnumUtil.GetValues<Sax.EligibilityType>())
{
EligibilityTypeData typeData = new EligibilityTypeData();
typeData.Code = model.EligibilityTypeCodes.GetCodeForType(eligibilityType);
if (StringUtil.IsBlank(typeData.Code)) continue;
typeData.Desc = Localiser.GetEligibilityTypeCaption(eligibilityType);
if (SaxUserEligibility.AllowsApplicablePeriod(eligibilityType))
typeData.Dates = true;
responseData.Types.Add(typeData);
}
// User account.
SysId userAccountId = SysId.ToSysId(model.UserAccount.Id);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
// User configuration.
if (SessionData_Online.UserConfig.Profile.Eligibility.HideInfo)
responseData.Config.hideInfo = true;
// Item filter.
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.NotDeleted;
prmUserEligibility.SortColumns.Add(AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.EligibilityType));
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserEligibilityCount(prmUserEligibility);
model.PopulatePagingTo(prmUserEligibility.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Id);
ColumnDfn colUserAccountId = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.UserAccountId);
ColumnDfn colRegionName = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Region_Name);
ColumnDfn colEligibilityType = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.EligibilityType);
ColumnDfn colInfo = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Info);
ColumnDfn colExpires = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.ExpiresOn);
ColumnDfn colStatus = AccountMgr.GetUserEligibilityColumnDfn(SaxUserEligibility.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,colUserAccountId,
colRegionName, colEligibilityType,
colInfo, colExpires,
colStatus,
};
// Sort columns.
prmUserEligibility.SortColumns.Add(colRegionName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserEligibilityQuery(columns, prmUserEligibility))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
Sax.EligibilityType eligibilityType = (Sax.EligibilityType)values.GetLong(colEligibilityType.DataName);
UserEligibilityResponseListData userEligibilityData = new UserEligibilityResponseListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
UserAccountId = model.EncryptId(new SysId(values.GetLong(colUserAccountId.DataName))),
RegionName = values.GetString(colRegionName.DataName),
EligibilityTypeDesc = Localiser.GetEligibilityTypeCaption(eligibilityType),
Info = values.GetString(colInfo.DataName),
ExpiresOnFmt = model.FormatDateForDisplay(values.GetDate(colExpires.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Eligibilities.Add(userEligibilityData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Eligibilities.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User Eligibility ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_UserEligibility : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserEligibility(SaxModel_Online model) : base(model) { }
// Eligibility details.
public UserEligibilityResponseItemData Eligibility { get; } = new UserEligibilityResponseItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/GetUserEligibility")]
public IActionResult Ajax_GetUserEligibility(Model_UserEligibility model)
{
String fn = GetType().Name + ".Ajax_GetUserEligibility";
String messageTitle = Localiser["account.caption.profile.eligibility"];
AjaxResponse_UserEligibility responseData = new AjaxResponse_UserEligibility(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId userEligibilityId = model.ExtractSysId(model.Eligibility.Id);
if (!SysId.IsValid(userEligibilityId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserEligibility userEligibility = AccountMgr.GetUserEligibilityById(userEligibilityId);
disposableItems.Add(userEligibility);
if (userEligibility == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Eligibility for Id: '" + model.Eligibility.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateUserEligibilityResponseItemData(model, userEligibility, responseData.Eligibility);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store User Eligibility ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_StoreUserEligibility : AjaxResponse_Data
{
// Construction.
public AjaxResponse_StoreUserEligibility(SaxModel_Online model) : base(model) { }
// Eligibility details.
public UserEligibilityResponseItemData Eligibility { get; } = new UserEligibilityResponseItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/StoreUserEligibility")]
public IActionResult Ajax_StoreUserEligibility(Model_UserEligibility model)
{
String fn = GetType().Name + ".Ajax_StoreUserEligibility";
String messageTitle = Localiser["account.caption.profile.eligibility"];
AjaxResponse_StoreUserEligibility responseData = new AjaxResponse_StoreUserEligibility(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateEligibilityDetails(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// User account.
SysId userAccountId = SysId.ToSysId(model.UserAccount.Id);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
// Validate User Eligibility Id against 'Exists' flag.
SysId userEligibilityId = model.ExtractSysId(model.Eligibility.Id);
bool eligibilityExists = StringUtil.ToBool(model.Eligibility.Exists);
if (eligibilityExists && !SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Id not specified for existing User Eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!eligibilityExists && SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Id specified for new User Eligibility: '" + model.Eligibility.Id + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxUserEligibility userEligibility = AccountMgr.GetUserEligibilityById(userEligibilityId);
disposableItems.Add(userEligibility);
if (userEligibility == null && SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Failed to retrieve User Eligibility for Id: '" + model.Eligibility.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate user eligibility properties.
if (userEligibility != null)
{
if (!userEligibility.UserAccountId.Equals(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent User Account Id specified for User Eligibility. Expected: " + userEligibility.UserAccountId + ". Found: " + userAccountId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// Region.
SysId regionId = model.Eligibility.RegionId != null ? model.DecryptId(model.Eligibility.RegionId) : userEligibility != null ? userEligibility.RegionId : null;
if (!SysId.IsValid(regionId))
{
// Shouldn't get here.
SaLogger.Warn(fn + " Region Id not supplied for " + (eligibilityExists ? "existing" : "new") + " eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SaxRegion.Data region = Internals.GetRegion(regionId);
// Eligibility type.
Sax.EligibilityType eligibilityType = model.Eligibility.Type != null ? model.Eligibility.getEligibilityType() : userEligibility != null ? userEligibility.EligibilityType : 0;
if (eligibilityType <= 0)
{
// Shouldn't get here.
SaLogger.Warn(fn + " Eligibility type not specified for " + (eligibilityExists ? "existing" : "new") + " eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Check for duplicate data.
// ToDo Include date range in checking for duplicate eligibility?
{
// First check without region.
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
if (userEligibility != null)
prmUserEligibility.IdsExcluded.Add(userEligibility.Id);
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.NotDeleted;
long eligibilityCount = AccountMgr.GetUserEligibilityCount(prmUserEligibility);
// Selected Eligibility types have a limit on the number of instances allowed.
if (eligibilityCount >= SaxUserEligibility.GetMaxInstances(eligibilityType))
{
SaLogger.Warn(fn + " Eligibility type instance limit reached. Type: " + eligibilityType.ToString() + ". Instances: " + eligibilityCount + ". " + GetLogSuffix());
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.duplicate"]));
}
// Now include region - only allow one eligibility of each type.
prmUserEligibility.RegionIdsIncluded.Add(regionId);
eligibilityCount = AccountMgr.GetUserEligibilityCount(prmUserEligibility);
if (eligibilityCount >= 1)
{
SaLogger.Warn(fn + " Duplicate Eligibility exists. RegionId: " + SysId.ToString(regionId) + ". Type: " + eligibilityType.ToString() + ". " + GetLogSuffix());
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.duplicate"]));
}
}
// If region or eligibility have changed, check whether an equivalent deleted eligibility exists.
// NB Can't modify the current eligibility with the modified values as it would conflict with the
// deleted item - unique on user, region and eligibility.
if (userEligibility != null && (!userEligibility.RegionId.Equals(regionId) || userEligibility.EligibilityType != eligibilityType))
{
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
prmUserEligibility.IdsExcluded.Add(userEligibility.Id);
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.RegionIdsIncluded.Add(regionId);
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.DeletedOnly;
SaxUserEligibility userEligibilityDeleted = AccountMgr.GetUserEligibility(prmUserEligibility);
if (userEligibilityDeleted != null)
{
disposableItems.Add(userEligibilityDeleted);
SaLogger.Info(fn + " Resurrecting User Eligibility with status: " + userEligibilityDeleted.StatusDesc + ". Id: " + SysId.ToString(userEligibilityDeleted.Id) + ". " + GetLogSuffix());
userEligibilityDeleted.Status = SaxUserEligibility.DefaultStatus;
// Delete current item and replace with previously deleted item.
if (!AccountMgr.DeleteUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to delete user eligibility: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
userEligibility = userEligibilityDeleted;
}
}
// If eligibility doesn't exist for the user, it may have been previously deleted.
if (userEligibility == null)
{
// See if there is an existing item that matches user, region and eligibility.
SaxUserEligibility.Params prmUserEligibility = new SaxUserEligibility.Params();
prmUserEligibility.UserAccountIdsIncluded.Add(userAccountId);
prmUserEligibility.RegionIdsIncluded.Add(regionId);
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
prmUserEligibility.StatusCat = SaxUserEligibility.StatusCat.DeletedOnly;
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
if (userEligibility == null)
{
// Try again for the user & region without the eligibility type.
prmUserEligibility.EligibilityTypesIncluded.Clear();
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
}
if (userEligibility == null)
{
// Try again for the user & eligibility type without the region.
prmUserEligibility.RegionIdsIncluded.Clear();
prmUserEligibility.EligibilityTypesIncluded.Add(eligibilityType);
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
}
if (userEligibility == null)
{
// Try again just for the user.
prmUserEligibility.RegionIdsIncluded.Clear();
prmUserEligibility.EligibilityTypesIncluded.Clear();
userEligibility = AccountMgr.GetUserEligibility(prmUserEligibility);
}
if (userEligibility != null)
{
disposableItems.Add(userEligibility);
SaLogger.Info(fn + " Resurrecting User Eligibility with status: " + userEligibility.StatusDesc + ". Id: " + SysId.ToString(userEligibility.Id) + ". " + GetLogSuffix());
userEligibility.Status = SaxUserEligibility.DefaultStatus;
}
else
{
userEligibility = AccountMgr.CreateUserEligibility(userAccountId, regionId, eligibilityType);
disposableItems.Add(userEligibility);
}
}
// Populate item properties.
userEligibility.RegionId = regionId;
userEligibility.EligibilityType = eligibilityType;
if (model.Eligibility.Info != null)
userEligibility.Info = model.Eligibility.Info;
if (model.Eligibility.ExpiresOn != null)
userEligibility.ExpiresOn = model.ParseDateFromForm(model.Eligibility.ExpiresOn);
// Store item.
if (!SysId.IsValid(userEligibility.Id))
{
if (!AccountMgr.InsertUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user eligibility: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!AccountMgr.UpdateUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user eligibility [Id:" + SysId.ToString(userEligibility.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Construct response.
PopulateUserEligibilityResponseItemData(model, userEligibility, responseData.Eligibility);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Remove User Eligibility ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_RemoveUserEligibility : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RemoveUserEligibility(SaxModel_Online model) : base(model) { }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/RemoveUserEligibility")]
public IActionResult Ajax_RemoveUserEligibility(Model_UserEligibility model)
{
String fn = GetType().Name + ".Ajax_RemoveUserEligibility";
String messageTitle = Localiser["account.caption.profile.eligibility"];
AjaxResponse_UserEligibility responseData = new AjaxResponse_UserEligibility(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// User account.
SysId userAccountId = SysId.ToSysId(model.UserAccount.Id);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// User Eligibility Id.
SysId userEligibilityId = model.ExtractSysId(model.Eligibility.Id);
if (!SysId.IsValid(userEligibilityId))
{
SaLogger.Warn(fn + " Id not specified to remove User Eligibility.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxUserEligibility userEligibility = AccountMgr.GetUserEligibilityById(userEligibilityId);
disposableItems.Add(userEligibility);
if (userEligibility == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Eligibility for Id: '" + model.Eligibility.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate user eligibility properties.
if (!userEligibility.UserAccountId.Equals(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent User Account Id specified for User Eligibility. Expected: " + userEligibility.UserAccountId + ". Found: " + userAccountId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Delete item.
if (!AccountMgr.DeleteUserEligibility(userEligibility, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to delete user eligibility [Id:" + SysId.ToString(userEligibility.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
// Construct response.
PopulateUserEligibilityResponseItemData(model, userEligibility, responseData.Eligibility);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,549 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller - Organisations - Ajax actions.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
/// <summary>Organisation properties for list population.</summary>
private class ProfileOrganisationListData : AjaxResponse_Data.ItemData_Base
{
public String Name { get; set; }
public String CategoryDesc { get; set; }
public String TypeDesc { get; set; }
public String LogoUrl { get; set; }
public String SportName { get; set; }
// Primary Region.
public class RegionData
{
public String Id { get; set; }
public String Name { get; set; }
public bool? Country { get; set; } = null;
}
public RegionData RegionPrimary { get; } = new RegionData();
// Associated properties.
public long MemberCount { get; set; }
public long OpportunityCount { get; set; }
public bool? Admin { get; set; } = null;
public bool? MemberAdmin { get; set; } = null;
public bool? OpportunitiyAdmin { get; set; } = null;
public bool? ScheduleAdmin { get; set; } = null;
public bool? ArticleAdmin { get; set; } = null;
}
/// <summary>Organisation properties for popup dialog and form.</summary>
private class ProfileOrganisationItemData : ProfileOrganisationListData
{
public String CategoryCode { get; set; }
public String TypeCode { get; set; }
public String SportId { get; set; }
public String About { get; set; }
public String Facilities { get; set; }
public List<AjaxResponse_Data.ContactMethodData> ContactMethods { get; set; }
public AjaxResponse_Data.AddressData Address { get; set; }
}
/// <summary>Populate organisation properties for list.</summary>
private void PopulateOrganisationResponseListData(SaxModel_Online model, SaxOrganisation organisation, SaxUserOrganisation userOrganisation, ProfileOrganisationListData data)
{
data.Id = model.EncryptId(organisation.Id);
data.Name = organisation.Name;
data.SportName = model.GetSportCaption(organisation.SportIdPrimary);
data.RegionPrimary.Name = model.GetRegionCaption(organisation.RegionIdPrimary);
// Associated properties.
data.MemberCount = AccountMgr.GetOrganisationMemberCount(organisation.Id);
data.OpportunityCount = AccountMgr.GetOrganisationOpportunityCount(organisation.Id);
if (userOrganisation != null)
{
List<String> roleCodes = userOrganisation.GetOrganisationRoleCodes();
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.Admin)))
data.Admin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.MemberAdmin)))
data.MemberAdmin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.OpportunityAdmin)))
data.OpportunitiyAdmin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.ScheduleAdmin)))
data.ScheduleAdmin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.ArticleAdmin)))
data.ArticleAdmin = true;
}
// Common properties.
data.CreatedOnFmt = Localiser.FormatDate(organisation.CreateStamp.On);
data.UpdatedOnFmt = Localiser.FormatDate(organisation.UpdateStamp.On);
data.StatusCode = organisation.StatusCode;
data.StatusDesc = organisation.StatusDesc;
}
/// <summary>Populate organisation properties for popup dialog and form.</summary>
private void PopulateOrganisationResponseItemData(SaxModel_Online model, SaxOrganisation organisation, SaxUserOrganisation userOrganisation, SaxItemAddress address, ProfileOrganisationItemData data)
{
PopulateOrganisationResponseListData(model, organisation, userOrganisation, (ProfileOrganisationListData)data);
data.CategoryCode = OrganisationCategoryCodes.GetCodeForType(organisation.OrganisationCategory);
data.CategoryDesc = Localiser.GetOrganisationCategoryCaption(organisation.OrganisationCategory);
data.TypeCode = OrganisationTypeCodes.GetCodeForType(organisation.OrganisationType);
data.TypeDesc = Localiser.GetOrganisationTypeCaption(organisation.OrganisationType);
data.SportId = model.GetEncryptedSportId(organisation.SportIdPrimary);
data.RegionPrimary.Id = model.GetEncryptedRegionId(organisation.RegionIdPrimary);
data.About = organisation.About;
data.Facilities = organisation.Facilities;
// Contact methods.
SaxContactData contactData = new SaxContactData(organisation.ContactData);
foreach (Sax.ContactMethod method in contactData.Methods)
{
if (data.ContactMethods == null) data.ContactMethods = new List<AjaxResponse_Data.ContactMethodData>();
data.ContactMethods.Add(new AjaxResponse_Data.ContactMethodData() { Method = method.ToString(), MethodCaption = Localiser.GetContactMethodCaption(method), Value = contactData.GetContactMethodValue(method) });
}
// Address.
if (address != null && !address.IsStatusDeleted)
{
data.Address = new AjaxResponse_Data.AddressData();
data.Address.Id = model.EncryptId(address.Id);
data.Address.Line1 = address.Line1;
data.Address.Line2 = address.Line2;
data.Address.Line3 = address.Line3;
data.Address.Town = address.Town;
data.Address.RegionNameProvince = address.Province;
data.Address.RegionNameCountry = address.Country;
data.Address.PostalCode = address.PostalCode;
}
}
//-------------------------------------------------------------------
// Account - Get Profile Organisations.
//-------------------------------------------------------------------
private class AjaxResponse_ProfileOrganisationList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ProfileOrganisationList(SaxModel_Online model) : base(model) { }
// Reference data.
public List<ItemCodeDesc> ContactMethods { get; } = new List<ItemCodeDesc>();
// Organisation list.
public List<ProfileOrganisationListData> Organisations { get; } = new List<ProfileOrganisationListData>();
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/GetProfileOrganisations")]
[Route("Account/Ajax/GetProfileOrganisationList")]
public IActionResult GetProfileOrganisationList(Model_Account model)
{
String fn = GetType().Name + ".GetProfileOrganisationList";
AjaxResponse_ProfileOrganisationList responseData = new AjaxResponse_ProfileOrganisationList(model);
String messageTitle = Localiser["account.caption.profile.organisations"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - contact methods.
List<Sax.ContactMethod> validContactMethods = SaxOrganisation.ValidContactMethods;
foreach (TypeCodeDesc contactMethod in ContactMethodCodes)
{
// Skip methods that aren't associated with Organisations.
if (!validContactMethods.Contains((Sax.ContactMethod)contactMethod.Type)) continue;
responseData.ContactMethods.Add(new AjaxResponse_Data.ItemCodeDesc() { Code = contactMethod.Code, Desc = Localiser.GetContactMethodCaption((Sax.ContactMethod)contactMethod.Type) });
}
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Query organisations that user is a member of.
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationStatusCat = SaxOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
List<SaxUserOrganisation> userOrganisations = AccountMgr.GetUserOrganisationList(prmUserOrganisation);
List<SysId> organisationIds = userOrganisations.GetOrganisationIds();
// Item columns.
// NB Although UserOrgansiation data is queried, the response data is a list of Organisations.
ColumnDfn colId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Name);
ColumnDfn colOrganisationCategory = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Category);
ColumnDfn colOrganisationType = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Type);
ColumnDfn colOrganisationSportName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Sport_Name);
ColumnDfn colOrganisationRegionName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Region_Name);
ColumnDfn colMemberCount = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Member_Count);
ColumnDfn colOpportunityCount = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Opportunity_Count);
ColumnDfn colStatus = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Status);
ColumnDfn colCreatedOn = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.CreateOn_Date);
ColumnDfn colLogoCount = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_LogoImage_Count);
ColumnDfn colRoleCodes = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationRoleCodes);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOrganisationName, colOrganisationCategory, colOrganisationType,
colOrganisationSportName, colOrganisationRegionName,
colMemberCount, colOpportunityCount,
colStatus, colStatus, colCreatedOn,
colLogoCount, colRoleCodes
};
// Sort columns.
colCreatedOn.SortAscending = false;
prmUserOrganisation.SortColumns.Add(colOrganisationName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(columns, prmUserOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId organisationId = new SysId(values.GetLong(colId.DataName));
Sax.OrganisationCategory organisationCategory = (Sax.OrganisationCategory)OrganisationCategoryCodes.GetTypeForCode(values.GetString(colOrganisationCategory.DataName));
Sax.OrganisationType organisationType = (Sax.OrganisationType)OrganisationTypeCodes.GetTypeForCode(values.GetString(colOrganisationType.DataName));
ProfileOrganisationListData organisationData = new ProfileOrganisationListData()
{
Id = model.EncryptId(organisationId),
Name = values.GetString(colOrganisationName.DataName),
CategoryDesc = Localiser.GetOrganisationCategoryCaption(organisationCategory),
TypeDesc = Localiser.GetOrganisationTypeCaption(organisationType),
SportName = values.GetString(colOrganisationSportName.DataName),
MemberCount = values.GetLong(colMemberCount.DataName),
OpportunityCount = values.GetLong(colOpportunityCount.DataName)
};
// Primary region.
organisationData.RegionPrimary.Name = values.GetString(colOrganisationRegionName.DataName);
// Logo.
if (values.GetLong(colLogoCount.DataName) > 0)
organisationData.LogoUrl = GetOrganisationLogoLink(organisationId, false);
// Roles.
List<String> roleCodes = StringListUtil.Split(values.GetString(colRoleCodes.DataName), SaxUserOrganisation.UserOrgnisationRoleDelim);
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.Admin)))
organisationData.Admin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.MemberAdmin)))
organisationData.MemberAdmin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.OpportunityAdmin)))
organisationData.OpportunitiyAdmin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.ScheduleAdmin)))
organisationData.ScheduleAdmin = true;
if (roleCodes.ContainsCI(OrganisationRoleCodes.GetCodeForType(Sax.OrganisationRole.ArticleAdmin)))
organisationData.ArticleAdmin = true;
responseData.Organisations.Add(organisationData);
}
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Organisations.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Get Profile Organisation.
//-------------------------------------------------------------------
private class AjaxResponse_ProfileOrganisation : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ProfileOrganisation(SaxModel_Online model) : base(model) { }
// Reference data.
public List<AjaxResponse_Data.ItemCodeDesc> Categories { get; } = new List<ItemCodeDesc>();
public List<AjaxResponse_Data.ItemCodeDesc> Types { get; } = new List<ItemCodeDesc>();
// Organisation list.
public ProfileOrganisationItemData Organisation { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/GetProfileOrganisation")]
public IActionResult GetProfileOrganisation(Model_Account model)
{
String fn = GetType().Name + ".GetProfileOrganisation";
AjaxResponse_ProfileOrganisation responseData = new AjaxResponse_ProfileOrganisation(model);
String messageTitle = Localiser["label.organisation"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId organisationId = model.ExtractSysId(model.Organisation.Id);
if (!SysId.IsValid(organisationId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve Organisation for Id: " + organisationId.ToString());
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Organisation address.
SaxItemAddress address = ItemDataMgr.GetFirstItemAddressByItemAndType(organisation.GetItemId());
disposableItems.Add(address);
// Check whether user is a member of this organisation.
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationByUserOrganisation(SessionUserAccountId, organisationId);
disposableItems.Add(userOrganisation);
if (userOrganisation != null)
{
// Make a note of the selected Organisation.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
}
// Construct response.
responseData.Organisation = new ProfileOrganisationItemData();
PopulateOrganisationResponseItemData(model, organisation, userOrganisation, address, responseData.Organisation);
// Reference data.
foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes)
{
// Categories only include Partner/Other if selected organisation has that category.
if (organisationCategory.Type != (int)organisation.OrganisationCategory && organisationCategory.Type.In(new[] { (int)Sax.OrganisationCategory.Partner, (int)Sax.OrganisationCategory.Other }))
continue;
responseData.Categories.Add(new AjaxResponse_Data.ItemCodeDesc() { Code = organisationCategory.Code, Desc = Localiser.GetOrganisationCategoryCaption((Sax.OrganisationCategory)organisationCategory.Type) });
}
foreach (TypeCodeDesc organisationType in OrganisationTypeCodes)
{
responseData.Types.Add(new AjaxResponse_Data.ItemCodeDesc() { Code = organisationType.Code, Desc = Localiser.GetOrganisationTypeCaption((Sax.OrganisationType)organisationType.Type) });
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Store Profile Organisation.
//-------------------------------------------------------------------
private class AjaxResponse_StoreProfileOrganisation : AjaxResponse_Data
{
// Construction.
public AjaxResponse_StoreProfileOrganisation(SaxModel_Online model) : base(model) { }
// Organisation list.
public ProfileOrganisationItemData Organisation { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/StoreProfileOrganisation")]
public IActionResult StoreProfileOrganisation(Model_Account model)
{
String fn = GetType().Name + ".StoreProfileOrganisation";
AjaxResponse_StoreProfileOrganisation responseData = new AjaxResponse_StoreProfileOrganisation(model);
String messageTitle = Localiser["label.organisation"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve organisation.
SysId organisationId = model.ExtractSysId(model.Organisation.Id);
if (!SysId.IsValid(organisationId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve Organisation for Id: " + organisationId.ToString() + ". " + GetLogSuffix());
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Organisation address.
SaxItemAddress organisationAddress = ItemDataMgr.GetFirstItemAddressByItemAndType(organisation.GetItemId());
disposableItems.Add(organisationAddress);
if (!StringUtil.IsEmpty(model.Organisation.Address.Id))
{
// Validate supplied Address Id.
SysId organisationAddressId = model.ExtractSysId(model.Organisation.Address.Id);
if (organisationAddress != null)
{
if (!organisationAddress.Id.Equals(organisationAddressId))
{
SaLogger.Warn(fn + " Organisation Address Id mismatch. Supplied: " + organisationAddressId + ". Found: " + organisationAddress.Id + ". " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
else
{
SaLogger.Warn(fn + " Organisation Address Id supplied (" + organisationAddressId + ") but no address found for Organisation. " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
else
{
// Address Id not supplied.
if (organisationAddress == null)
{
organisationAddress = ItemDataMgr.CreateItemAddress(organisation.GetItemId(), SaxItemAddress.DefaultOrganisationAddressType);
disposableItems.Add(organisationAddress);
}
else if (!organisationAddress.IsStatusDeleted)
{
SaLogger.Warn(fn + " Organisation Address Id not supplied but no address found for Organisation. Id: " + organisationAddress.Id + ". " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// Check whether user is a member of this organisation.
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationByUserOrganisation(SessionUserAccountId, organisationId);
disposableItems.Add(userOrganisation);
if (userOrganisation == null)
{
SaLogger.Warn(fn + " Account: " + SessionData.User.EmailAddress + ". OrganisationId: " + organisationId.ToString() + ". User Organisation not found. " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!userOrganisation.GetOrganisationRoles().Contains(Sax.OrganisationRole.Admin))
{
SaLogger.Warn(fn + " Account: " + SessionData.User.EmailAddress + ". OrganisationId: " + organisationId.ToString() + ". User does not have Admin role to update organisation. " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Validate organisation data.
model.ValidateProfileOrganisationData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Regions.
SysId regionIdCountry = model.ExtractSysId(model.Organisation.RegionIdPrimary);
SaxRegion.Data regionCountry = Internals.GetRegion(regionIdCountry);
if (SysId.IsValid(regionIdCountry) && regionCountry == null)
{
SaLogger.Warn(fn + " Country region not found. Id: " + model.Organisation.RegionIdPrimary + ". " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SysId regionIdAddressProvince = model.ExtractSysId(model.Organisation.Address.RegionIdProvince);
SaxRegion.Data regionProvince = Internals.GetRegion(regionIdAddressProvince);
if (SysId.IsValid(regionIdAddressProvince) && regionProvince == null)
{
SaLogger.Warn(fn + " Province region not found. Id: " + model.Organisation.Address.RegionIdProvince + ". " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Update organisation.
if (model.Organisation.Name != null)
organisation.Name = model.Organisation.Name;
if (model.Organisation.Type != null)
organisation.OrganisationType = model.Organisation.GetOrganisationType();
if (model.Organisation.Category != null)
organisation.OrganisationCategory = model.Organisation.GetOrganisationCategory();
if (model.Organisation.SportId != null)
organisation.SportIdPrimary = model.ExtractSysId(model.Organisation.SportId);
if (model.Organisation.RegionIdPrimary != null)
organisation.RegionIdPrimary = model.ExtractSysId(model.Organisation.RegionIdPrimary);
if (model.Organisation.About != null)
organisation.About = model.Organisation.About;
if (model.Organisation.Facilities != null)
organisation.Facilities = model.Organisation.Facilities;
if (model.Organisation.ContactData != null)
organisation.ContactData = model.Organisation.ContactData.GetContactData();
if (!AccountMgr.UpdateOrganisation(organisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update organisation - " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Store address.
if (model.Organisation.Address.IsSpecified())
{
// The form includes address fields.
if (model.Organisation.Address.IsPopulated())
{
// At least one address field has been populated.
if (model.Organisation.Address.Line1 != null)
organisationAddress.Line1 = model.Organisation.Address.Line1;
if (model.Organisation.Address.Line2 != null)
organisationAddress.Line2 = model.Organisation.Address.Line2;
if (model.Organisation.Address.Line3 != null)
organisationAddress.Line3 = model.Organisation.Address.Line3;
if (model.Organisation.Address.Town != null)
organisationAddress.Town = model.Organisation.Address.Town;
if (regionProvince != null)
{
organisationAddress.Province = regionProvince.Name;
organisationAddress.RegionIdProvince = regionProvince.Id;
}
else
{
if (model.Organisation.Address.Province != null)
organisationAddress.Province = model.Organisation.Address.Province;
}
if (regionCountry != null)
{
organisationAddress.Country = regionCountry.Name;
organisationAddress.RegionIdCountry = regionCountry.Id;
}
else
{
if (model.Organisation.Address.Country != null)
organisationAddress.Country = model.Organisation.Address.Country;
}
organisationAddress.PostalCode = model.Organisation.Address.PostalCode;
organisationAddress.Status = SaxItemAddress.DefaultStatus;
// Store address.
if (!SysId.IsValid(organisationAddress.Id))
{
// Create new address.
if (!ItemDataMgr.InsertItemAddress(organisationAddress, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert organisation address - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
else
{
// Update existing address.
if (!ItemDataMgr.UpdateItemAddress(organisationAddress, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update organisation address - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
}
else
{
// Address not specified - delete it.
if (!ItemDataMgr.DeleteItemAddress(organisationAddress, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to delete organisation address - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
}
// Construct response.
responseData.Organisation = new ProfileOrganisationItemData();
PopulateOrganisationResponseItemData(model, organisation, userOrganisation, organisationAddress, responseData.Organisation);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,426 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
using static Saxonite.Net.Standard.Base.App.Sax.Config;
using static Saxonite.Net.Standard.Base.Bus.SaxArticle;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller - Sports - Ajax actions.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
// Data definitions.
private class ProfileSportResponseListData : AjaxResponse_Data.ItemData_Base
{
public String SportId { get; set; }
public String SportName { get; set; }
public String SportSpecialityLabel { get; set; }
public String SportParticipating { get; set; }
public String SportPreparing { get; set; }
public String SportOfficiating { get; set; }
}
private class ProfileSportResponseItemData : ProfileSportResponseListData
{
public String SportAbout { get; set; }
public List<SportContactData> Contacts { get; set; }
public String SportParticipatingCode { get; set; }
public String SportPreparingCode { get; set; }
public String SportOfficiatingCode { get; set; }
}
public class SportContactData
{
public String ContactType { get; set; }
public String ContactTypeDesc { get; set; }
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
public String EmailAddress { get; set; }
public String Phone { get; set; }
}
private void PopulateProfileSportResponseListData(Model_Account model, SaxUserSport userSport, SaxSport sport, ProfileSportResponseListData data)
{
data.Id = model.EncryptId(userSport.Id);
data.SportId = model.GetEncryptedSportId(userSport.SportId);
data.SportName = sport.Name;
data.SportSpecialityLabel = model.GetSportSpecialityLabel(userSport.SportId);
data.SportParticipating = Localiser.GetSportParticipatingCaption(userSport.Participating);
data.SportPreparing = Localiser.GetSportPreparingCaption(userSport.Preparing);
data.SportOfficiating = Localiser.GetSportOfficiatingCaption(userSport.Officiating);
}
private void PopulateProfileSportResponseItemData(Model_Account model, SaxUserSport userSport, SaxSport sport, ProfileSportResponseItemData data)
{
PopulateProfileSportResponseListData(model, userSport, sport, data);
data.SportAbout = userSport.About;
// Sport Contacts.
SaxSportContactsData sportContacts = new SaxSportContactsData(userSport.ContactsData);
foreach (Sax.SportContactType contactType in sportContacts.ContactTypes)
{
if (data.Contacts == null) data.Contacts = new List<SportContactData>();
SportContactData sportContact =
new SportContactData()
{
ContactType = SportContactTypeCodes.GetCodeForType(contactType),
ContactTypeDesc = Localiser.GetSportContactTypeCaption(contactType),
Title = sportContacts.GetContactTitle(contactType),
FirstName = sportContacts.GetContactFirstName(contactType),
LastName = sportContacts.GetContactLastName(contactType),
EmailAddress = sportContacts.GetContactEmailAddress(contactType),
Phone = sportContacts.GetContactPhone(contactType),
};
data.Contacts.Add(sportContact);
}
data.SportParticipatingCode = SportParticipatingCodes.GetCodeForType(userSport.Participating);
data.SportPreparingCode = SportPreparingCodes.GetCodeForType(userSport.Preparing);
data.SportOfficiatingCode = SportOfficiatingCodes.GetCodeForType(userSport.Officiating);
data.CreatedOnFmt = model.FormatDateForDisplay(userSport.CreateStamp.On);
data.StatusCode = userSport.StatusCode;
data.StatusDesc = userSport.StatusDesc;
}
//-------------------------------------------------------------------
// Account - Get Profile Sports.
//-------------------------------------------------------------------
private class AjaxResponse_ProfileSportsData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ProfileSportsData(SaxModel_Online model) : base(model) { }
// Reference data.
public List<ItemCodeDesc> ContactTypes { get; } = new List<ItemCodeDesc>();
public List<ItemCodeDesc> Titles { get; } = new List<ItemCodeDesc>();
// Sport list.
public List<ProfileSportResponseListData> Sports { get; } = new List<ProfileSportResponseListData>();
}
// Ajax request.
[HttpPost]
[Route("Account/Ajax/GetProfileSports")]
public IActionResult GetProfileSports(Model_Account model)
{
String fn = GetType().Name + ".GetProfileSports";
AjaxResponse_ProfileSportsData responseData = new AjaxResponse_ProfileSportsData(model);
String messageTitle = Localiser["account.caption.profile.sports"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - Contact types.
List<Sax.SportContactType> validContactTypes = new List<Sax.SportContactType>(EnumUtil.GetValues<Sax.SportContactType>());
foreach (TypeCodeDesc contactType in SportContactTypeCodes)
{
if (!validContactTypes.Contains((Sax.SportContactType)contactType.Type)) continue;
responseData.ContactTypes.Add(new AjaxResponse_Data.ItemCodeDesc() { Code = contactType.Code, Desc = Localiser.GetSportContactTypeCaption((Sax.SportContactType)contactType.Type) });
}
// Reference data - Titles.
foreach (var title in model.TitlesForSelect)
responseData.Titles.Add(new AjaxResponse_Data.ItemCodeDesc() { Code = title.Value, Desc = title.Text });
// Item filter.
SaxUserSport.Params prmUserSport = new SaxUserSport.Params();
prmUserSport.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserSport.StatusCat = SaxUserSport.StatusCat.NotDeleted;
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserSportCount(prmUserSport);
model.PopulatePagingTo(prmUserSport.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Id);
ColumnDfn colSportId = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportId);
ColumnDfn colSportName = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Sport_Name);
ColumnDfn colParticipating = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportParticipating);
ColumnDfn colPreparing = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportPreparing);
ColumnDfn colOfficiating = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportOfficiating);
ColumnDfn colStatus = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colSportId, colSportName,
colParticipating, colPreparing, colOfficiating,
colStatus,
};
// Sort columns.
prmUserSport.SortColumns.Add(colSportName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserSportQuery(columns, prmUserSport))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
ProfileSportResponseListData userSportData = new ProfileSportResponseListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
SportId = model.GetEncryptedSportId(sportId),
SportName = values.GetString(colSportName.DataName),
SportSpecialityLabel = model.GetSportSpecialityLabel(sportId),
SportParticipating = Localiser.GetSportParticipatingCaption((Sax.SportParticipating)values.GetLong(colParticipating.DataName)),
SportPreparing = Localiser.GetSportPreparingCaption((Sax.SportPreparing)values.GetLong(colPreparing.DataName)),
SportOfficiating = Localiser.GetSportOfficiatingCaption((Sax.SportOfficiating)values.GetLong(colOfficiating.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Sports.Add(userSportData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Sports.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Profile Sport ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_ProfileSport : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ProfileSport(SaxModel_Online model) : base(model) { }
// Sport details.
public ProfileSportResponseItemData Sport { get; } = new ProfileSportResponseItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/GetProfileSport")]
public IActionResult Ajax_GetProfileSport(Model_Account model)
{
String fn = GetType().Name + ".Ajax_GetProfileSport";
String messageTitle = Localiser["account.caption.profile.sport"];
AjaxResponse_ProfileSport responseData = new AjaxResponse_ProfileSport(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId userSportId = model.ExtractSysId(model.UserSportId);
if (!SysId.IsValid(userSportId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserSport userSport = AccountMgr.GetUserSportById(userSportId);
disposableItems.Add(userSport);
if (userSport == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Sport for Id: '" + model.UserSportId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Retrieve sport.
SaxSport sport = ReferenceMgr.GetSportById(userSport.SportId);
disposableItems.Add(sport);
if (sport == null)
{
SaLogger.Warn(fn + " Failed to retrieve Sport for Id: '" + userSport.SportId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateProfileSportResponseItemData(model, userSport, sport, responseData.Sport);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Validate Profile Sport Contact ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_ProfileSportContact : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ProfileSportContact(SaxModel_Online model) : base(model) { }
}
[HttpPost]
[Route("Account/Ajax/ValidateProfileSportContact")]
public IActionResult Ajax_ValidateProfileSportContact(Model_UserSportContact model)
{
String fn = GetType().Name + ".Ajax_ValidateProfileSportContact";
String messageTitle = Localiser["account.caption.profile.sport.contact"];
AjaxResponse_ProfileSportContact responseData = new AjaxResponse_ProfileSportContact(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateUserSportContactData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Construct response.
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Profile Sport ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Account/Ajax/StoreProfileSport")]
public IActionResult Ajax_StoreProfileSport(Model_Account model)
{
String fn = GetType().Name + ".Ajax_StoreProfileSport";
String messageTitle = Localiser["account.caption.profile.sport"];
AjaxResponse_ProfileSport responseData = new AjaxResponse_ProfileSport(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateProfileSportData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Validate Profile Sport Id against 'Exists' flag.
SysId userSportId = model.ExtractSysId(model.UserSportId);
bool profileSportExists = StringUtil.ToBool(model.ProfileSportExists);
if (profileSportExists && !SysId.IsValid(userSportId))
{
SaLogger.Warn(fn + " Id not specified for existing Profile Sport.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!profileSportExists && SysId.IsValid(userSportId))
{
SaLogger.Warn(fn + " Id specified for new Profile Sport: '" + model.UserSportId + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve the sport.
SysId sportId = model.ExtractSysId(model.ProfileSportId);
SaxSport sport = ReferenceMgr.GetSportById(sportId);
disposableItems.Add(sport);
if (sport == null)
{
SaLogger.Warn(fn + " Failed to retrieve Sport for Id: " + sportId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxUserSport userSport = AccountMgr.GetUserSportById(userSportId);
disposableItems.Add(userSport);
if (userSport == null && SysId.IsValid(userSportId))
{
SaLogger.Warn(fn + " Failed to retrieve User Sport for Id: '" + model.UserSportId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate user sport properties.
if (userSport != null)
{
if (!userSport.SportId.Equals(sportId))
{
SaLogger.Warn(fn + " Inconsistent Sport Id specified for User Sport. Expected: " + userSport.SportId + ". Found: " + sportId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// If sport doesn't exist for the user, it may have been previously deleted.
if (userSport == null)
{
SaxUserSport.Params prmUserSport = new SaxUserSport.Params();
prmUserSport.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserSport.SportIdsIncluded.Add(sportId);
prmUserSport.StatusCat = SaxUserSport.StatusCat.Any;
userSport = AccountMgr.GetUserSport(prmUserSport);
if (userSport != null)
{
disposableItems.Add(userSport);
if (userSport.Status == SaStatus.Deleted)
{
SaLogger.Info(fn + " Resurrecting User Sport with status: " + userSport.StatusDesc + ". Id: " + SysId.ToString(userSport.Id));
userSport.Status = SaxUserSport.DefaultStatus;
}
else
{
responseData.SetControlError(nameof(model.ProfileSportId), Localiser["msg.validation.duplicate.value"]);
return Json(responseData);
}
}
else
{
userSport = AccountMgr.CreateUserSport(SessionUserAccountId, sportId);
disposableItems.Add(userSport);
}
}
// Populate item properties.
if (model.ProfileSportAbout != null)
userSport.About = model.ProfileSportAbout;
if (model.Contacts.ContactsData != null)
userSport.ContactsData = model.Contacts.ContactsData.ToXml();
if (model.ProfileSportParticipatingCode != null)
userSport.Participating = model.ProfileSportParticipating;
if (model.ProfileSportPreparingCode != null)
userSport.Preparing = model.ProfileSportPreparing;
if (model.ProfileSportOfficiatingCode != null)
userSport.Officiating = model.ProfileSportOfficiating;
// Store item.
if (!SysId.IsValid(userSport.Id))
{
if (!AccountMgr.InsertUserSport(userSport, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user sport: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!AccountMgr.UpdateUserSport(userSport, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user sport [Id:" + SysId.ToString(userSport.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Refresh list of profile sports for Activity Index.
RefreshSessionProfileSports();
// Construct response.
PopulateProfileSportResponseItemData(model, userSport, sport, responseData.Sport);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,370 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Standard.Base.App;
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.Helpers;
using System.IO;
using System.Drawing;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller - User Sport Experience - Ajax actions.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
// Common response data.
private class SportExperienceResponseListData : AjaxResponse_Data.ItemData_Base
{
public String UserSportId { get; set; }
public String Period { get; set; }
public String PeriodFrom { get; set; }
public String PeriodTo { get; set; }
public String OrganisationName { get; set; }
public String Summary { get; set; }
public String Team { get; set; }
public String League { get; set; }
public String Speciality { get; set; }
// Regions.
public String RegionCaptionNation { get; set; }
public String RegionCaptionProvince { get; set; }
public string RegionRepresentation { get; set; }
}
private class SportExperienceResponseItemData : SportExperienceResponseListData
{
public String PeriodFrom_YMD { get; set; }
public String PeriodTo_YMD { get; set; }
public String OrganisationId { get; set; }
public String Awards { get; set; }
public String RegionIdNation { get; set; }
public String RegionIdProvince { get; set; }
}
private void PopulateSportExperienceResponseListData(Model_UserSportExperience model, SportExperienceResponseListData data, SaxUserSport userSport, SaxUserSportExperience userSportExperience)
{
data.Id = model.EncryptId(userSportExperience.Id);
data.UserSportId = model.EncryptId(userSportExperience.UserSportId);
data.PeriodFrom = model.FormatDateForForm(userSportExperience.PeriodFrom);
data.PeriodTo = model.FormatDateForForm(userSportExperience.PeriodTo);
data.OrganisationName = userSportExperience.OrganisationName;
data.Summary = userSportExperience.Summary;
data.Team = userSportExperience.Team;
data.League = userSportExperience.League;
data.Speciality = userSportExperience.Speciality;
// Regions.
data.RegionCaptionNation = Localiser.GetRegionName(Internals.GetRegion(userSportExperience.RegionIdNation));
data.RegionCaptionProvince = Localiser.GetRegionName(Internals.GetRegion(userSportExperience.RegionIdProvince));
List<String> locations = new List<string>() { data.RegionCaptionProvince, data.RegionCaptionNation };
data.RegionRepresentation = locations.Concat(", ", "", "", true, true);
}
private void PopulateSportExperienceResponseItemData(Model_UserSportExperience model, SportExperienceResponseItemData data, SaxUserSport userSport, SaxUserSportExperience userSportExperience)
{
PopulateSportExperienceResponseListData(model, data, userSport, userSportExperience);
data.PeriodFrom_YMD = DateUtil.FormatDateForJavascript(userSportExperience.PeriodFrom);
data.PeriodTo_YMD = DateUtil.FormatDateForJavascript(userSportExperience.PeriodTo);
data.Period = data.PeriodFrom + " - " + data.PeriodTo;
data.OrganisationId = model.EncryptId(userSportExperience.OrganisationId);
data.Awards = userSportExperience.Awards;
// Regions.
data.RegionIdNation = model.GetEncryptedRegionId(userSportExperience.RegionIdNation);
data.RegionIdProvince = model.GetEncryptedRegionId(userSportExperience.RegionIdProvince);
}
//-------------------------------------------------------------------
// Utilites.
//-------------------------------------------------------------------
// User Sport - Get Experience list.
//-------------------------------------------------------------------
private class AjaxResponse_UserSportExperienceList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserSportExperienceList(SaxModel_Online model) : base(model) { }
// Contact details.
public List<SportExperienceResponseListData> Experiences { get; } = new List<SportExperienceResponseListData>();
// Sport details.
public class SportData
{
public class SpecialityData
{
public bool? NotRelevant { get; set; } = null;
public String Label { get; set; }
}
public SpecialityData Speciality { get; } = new SpecialityData();
}
public SportData Sport { get; } = new SportData();
}
// Ajax request.
[HttpPost]
[Route("UserSport/Ajax/GetExperienceList")]
public IActionResult GetUserSportExperienceList(Model_UserSportExperience model)
{
String fn = GetType().Name + ".GetUserSportExperienceList";
AjaxResponse_UserSportExperienceList responseData = new AjaxResponse_UserSportExperienceList(model);
String messageTitle = Localiser["account.caption.profile.sports"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate User Sport Id against user account.
SysId userSportId = model.ExtractSysId(model.UserSportId);
SaxUserSport userSport = AccountMgr.GetUserSportById(userSportId);
disposableItems.Add(userSport);
if (userSport == null || !userSport.UserAccountId.Equals(SessionUserAccountId))
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
// Sport details.
if (model.SportSpecialityNotRelevant(userSport.SportId)) responseData.Sport.Speciality.NotRelevant = true;
responseData.Sport.Speciality.Label = model.GetSportSpecialityLabel(userSport.SportId);
// Columns.
ColumnDfn colId = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.Id);
ColumnDfn colUserSportId = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.UserSportId);
ColumnDfn colPeriodFrom = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.PeriodFrom);
ColumnDfn colPeriodTo = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.PeriodTo);
ColumnDfn colOrganisationId = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.Organisation_Name);
ColumnDfn colSummary = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.Summary);
ColumnDfn colTeam = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.Team);
ColumnDfn colLeague = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.League);
ColumnDfn colSpeciality = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.Speciality);
ColumnDfn colNotes = AccountMgr.GetUserSportExperienceColumnDfn(SaxUserSportExperience.Column.Notes);
List<ColumnDfn> columnsToQuery = new List<ColumnDfn>()
{
colId, colUserSportId, colPeriodFrom, colPeriodTo,
colOrganisationId, colOrganisationName,
colSummary, colTeam, colLeague, colSpeciality, colNotes
};
// Filter.
SaxUserSportExperience.Params prmUserSportExperience = new SaxUserSportExperience.Params();
prmUserSportExperience.UserSportIdsIncluded.Add(model.ExtractSysId(model.UserSportId));
prmUserSportExperience.StatusCat = SaxUserSportExperience.StatusCat.NotDeleted;
// Sort columns.
colPeriodFrom.SortAscending = false;
prmUserSportExperience.SortColumns.Add(colPeriodFrom);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserSportExperienceQuery(columnsToQuery, prmUserSportExperience))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SportExperienceResponseListData itemData = new SportExperienceResponseListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
UserSportId = model.EncryptId(new SysId(values.GetLong(colUserSportId.DataName))),
PeriodFrom = model.FormatDateForForm(values.GetDate(colPeriodFrom.DataName)),
PeriodTo = model.FormatDateForForm(values.GetDate(colPeriodTo.DataName)),
OrganisationName = AccountMgr.GetUserSportExperienceDataValue(colOrganisationName, values).AsString(),
Summary = values.GetString(colSummary.DataName),
Team = values.GetString(colTeam.DataName),
League = values.GetString(colLeague.DataName),
Speciality = values.GetString(colSpeciality.DataName),
};
itemData.Period = itemData.PeriodFrom + " - " + itemData.PeriodTo;
responseData.Experiences.Add(itemData);
}
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// User Sport - Get Experience.
//-------------------------------------------------------------------
private class AjaxResponse_UserSportExperienceData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserSportExperienceData(SaxModel_Online model) : base(model) { }
// Contact details.
public SportExperienceResponseItemData Experience { get; set; }
}
// Ajax request.
[HttpPost]
[Route("UserSport/Ajax/GetExperience")]
public IActionResult GetUserSportExperience(Model_UserSportExperience model)
{
String fn = GetType().Name + ".GetUserSportExperience";
AjaxResponse_UserSportExperienceData responseData = new AjaxResponse_UserSportExperienceData(model);
String messageTitle = Localiser["account.caption.profile.sports"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate User Sport Experience Id against user account.
SysId userSportExperienceId = model.ExtractSysId(model.UserSportExperienceId);
SaxUserSportExperience userSportExperience = AccountMgr.GetUserSportExperienceById(userSportExperienceId);
disposableItems.Add(userSportExperience);
SaxUserSport userSport = AccountMgr.GetUserSportById(userSportExperience?.UserSportId ?? null);
disposableItems.Add(userSport);
if (userSportExperience == null || userSport == null || !userSport.UserAccountId.Equals(SessionUserAccountId))
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
// Populate data to response.
responseData.Experience = new SportExperienceResponseItemData();
PopulateSportExperienceResponseItemData(model, responseData.Experience, userSport, userSportExperience);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Store User Sport Experience.
//-------------------------------------------------------------------
private class AjaxResponse_StoreUserSportExperience : AjaxResponse_Data
{
// Construction.
public AjaxResponse_StoreUserSportExperience(SaxModel_Online model) : base(model) { }
// Contact details.
public SportExperienceResponseItemData Experience { get; set; }
}
// Ajax request.
[HttpPost]
[Route("UserSport/Ajax/StoreExperience")]
public IActionResult StoreUserSportExperience(Model_UserSportExperience model)
{
String fn = GetType().Name + ".StoreUserSportExperience";
AjaxResponse_StoreUserSportExperience responseData = new AjaxResponse_StoreUserSportExperience(model);
String messageTitle = Localiser["account.caption.profile.sports"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate data.
model.ValidateProfileSportData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Validate User Sport Experience Id.
SysId userSportExperienceId = model.ExtractSysId(model.UserSportExperienceId);
if (SysId.IsValid(userSportExperienceId) != StringUtil.ToBool(model.UserSportExperienceExists))
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
// Retrieve User Sport Experience.
SaxUserSportExperience userSportExperience = null;
SaxUserSport userSport = null;
if (SysId.IsValid(userSportExperienceId))
{
userSportExperience = AccountMgr.GetUserSportExperienceById(userSportExperienceId);
disposableItems.Add(userSportExperience);
userSport = AccountMgr.GetUserSportById(userSportExperience?.UserSportId ?? null);
disposableItems.Add(userSport);
if (userSportExperience == null || userSport == null || !userSport.UserAccountId.Equals(SessionUserAccountId))
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
}
else
{
// Creating new User Sport Experience.
// Retrieve User Sport and validate.
SysId userSportId = model.ExtractSysId(model.UserSportId);
userSport = AccountMgr.GetUserSportById(userSportId);
disposableItems.Add(userSport);
if (userSport == null || !userSport.UserAccountId.Equals(SessionUserAccountId))
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.something.gone.wrong"]));
// Create User Sport Experience.
userSportExperience = AccountMgr.CreateUserSportExperience(userSport.Id);
disposableItems.Add(userSportExperience);
}
// Populate properties.
userSportExperience.Changed = false;
if (model.ExperiencePeriodFrom != null)
userSportExperience.PeriodFrom = model.ParseDateFromForm(model.ExperiencePeriodFrom);
if (model.ExperiencePeriodTo != null)
userSportExperience.PeriodTo = model.ParseDateFromForm(model.ExperiencePeriodTo);
if (model.ExperienceOrganisationId != null)
userSportExperience.OrganisationId = model.ExtractSysId(model.ExperienceOrganisationId);
if (model.ExperienceOrganisationName != null)
userSportExperience.OrganisationName = model.ExperienceOrganisationName;
if (model.ExperienceSummary != null)
userSportExperience.Summary = model.ExperienceSummary;
if (model.ExperienceTeam != null)
userSportExperience.Team = model.ExperienceTeam;
if (model.ExperienceLeague != null)
userSportExperience.League = model.ExperienceLeague;
if (model.ExperienceSpeciality != null)
userSportExperience.Speciality = model.ExperienceSpeciality;
if (model.ExperienceAwards != null)
userSportExperience.Awards = model.ExperienceAwards;
// Regions.
if (model.ExperienceRegionIdNation != null)
userSportExperience.RegionIdNation = model.ExtractSysId(model.ExperienceRegionIdNation);
if (model.ExperienceRegionIdProvince != null)
userSportExperience.RegionIdProvince = model.ExtractSysId(model.ExperienceRegionIdProvince);
// Store Experience.
if (!SysId.IsValid(userSportExperience.Id))
{
if (AccountMgr.InsertUserSportExperience(userSportExperience, DbUpdateInfo))
{
responseData.SetNotifyMessage(messageTitle, Localiser["msg.item.created"]);
}
else
{
SaLogger.Warn(fn + " Failed to insert user sport experience: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (AccountMgr.UpdateUserSportExperience(userSportExperience, DbUpdateInfo))
{
responseData.SetNotifyMessage(messageTitle, Localiser["msg.item.updated"]);
}
else
{
SaLogger.Warn(fn + " Failed to update user sport experience: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Populate data to response.
responseData.Experience = new SportExperienceResponseItemData();
PopulateSportExperienceResponseItemData(model, responseData.Experience, userSport, userSportExperience);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Account Controller.
//=======================================================================
public partial class AccountController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public AccountController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Account model = new Model_Account(Localiser);
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Profile()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Account model = new Model_Account(Localiser);
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Sports()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Account model = new Model_Account(Localiser);
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Organisations()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Account model = new Model_Account(Localiser);
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,453 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Activity Controller - Ajax actions only.
//=======================================================================
public partial class ActivityController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public ActivityController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{ }
//-------------------------------------------------------------------
// Data definitions.
/// <summary>Activity details for home page list.</summary>
private class ActivityResponseData
{
public short ItemType { get; set; }
public String ItemTypeClass { get; set; }
public String ItemId { get; set; }
public String Organisation { get; set; }
public String OrganisationLogoUrl { get; set; }
public String Author { get; set; }
public String Caption { get; set; }
public String Blurb { get; set; }
public String ThumbnailUrl { get; set; }
public String Sport { get; set; }
public String Region { get; set; }
public int? Reaction { get; set; } = null;
public String PublishedOnFmt { get; set; }
}
/// <summary>Article activity info. </summary>
private class ActivityResponseData_Article
{
public ActivityResponseData_Article(SaxController_Online controller, SaxModel_Online model, SaxArticle article, SaxOrganisation organisation)
{
Organisation = organisation?.Name ?? null;
if (controller.ItemDataMgr.ItemHasActiveImage(organisation?.GetItemId() ?? null, Sax.ImageType.OrganisationLogo))
OrganisationLogoUrl = controller.GetOrganisationLogoLink(organisation.Id, false);
Author = model.GetUserAccountDisplayName(article.UserAccountIdAuthor);
Headline = article.Headline;
Summary = article.Summary;
Sport = model.GetSportCaption(article.SportIdPrimary);
Region = model.GetRegionCaption(article.RegionIdPrimary);
PublishedOnFmt = model.Localiser.FormatDate(article.PublishedAt);
SaxArticle.ContentData content = SaxArticle.ExtractContent(article.BodyData);
foreach (SaxArticle.ContentData.SegmentData segment in content.Segments)
Content.Segments.Add(new ContentData.SegmentData(controller, segment));
}
public String Organisation { get; set; }
public String OrganisationLogoUrl { get; set; }
public String Author { get; set; }
public String Headline { get; set; }
public String Summary { get; set; }
public String Sport { get; set; }
public String Region { get; set; }
public String PublishedOnFmt { get; set; }
public class ContentData
{
public class SegmentData
{
public SegmentData(SaxController_Online controller, SaxArticle.ContentData.SegmentData segment)
{
if (segment.Body.IsValid())
{
Body = new BodyData();
Body.Html = segment.Body.Html;
}
else if (segment.Image.IsValid())
{
Image = new ImageData();
Image.Link = controller.GetItemImageLink(segment.Image.ItemImageId);
}
}
public class BodyData
{
public String Html { get; set; }
}
public BodyData Body { get; }
public class ImageData
{
public String Link { get; set; }
}
public ImageData Image { get; }
}
public List<SegmentData> Segments { get; } = new List<SegmentData>();
}
public ContentData Content { get; } = new ContentData();
}
/// <summary>Event activity info. </summary>
private class ActivityResponseData_Event
{
public ActivityResponseData_Event(SaxController_Online controller, SaxModel_Online model, SaxOrganisationEvent organisationEvent, SaxOrganisation organisation)
{
Organisation = model.GetOrganisationName(organisationEvent.OrganisationId);
if (controller.ItemDataMgr.ItemHasActiveImage(organisation?.GetItemId() ?? null, Sax.ImageType.OrganisationLogo))
OrganisationLogoUrl = controller.GetOrganisationLogoLink(organisation.Id, false);
EventAt = model.FormatDateForDisplay(organisationEvent.EventAt);
if (organisationEvent.EventAt.Hour > 0 || organisationEvent.EventAt.Minute > 0)
EventAt += DateUtil.Format(organisationEvent.EventAt, " HH:ss");
Summary = organisationEvent.Summary;
Details = organisationEvent.Details;
Sport = model.GetSportCaption(organisationEvent.SportIdPrimary);
Region = model.GetRegionCaption(organisationEvent.RegionIdCountry);
PublishedOnFmt = model.Localiser.FormatDate(organisationEvent.PublishedAt);
}
public String Organisation { get; set; }
public String OrganisationLogoUrl { get; set; }
public String EventAt { get; set; }
public String Summary { get; set; }
public String Details { get; set; }
public String Sport { get; set; }
public String Region { get; set; }
public String PublishedOnFmt { get; set; }
}
/// <summary>Opportunity activity info. </summary>
private class ActivityResponseData_Opportunity
{
public ActivityResponseData_Opportunity(SaxController_Online controller, SaxModel_Online model, SaxOrganisationOpportunity opportunity, SaxOrganisation organisation)
{
Organisation = model.GetOrganisationName(opportunity.OrganisationId);
if (controller.ItemDataMgr.ItemHasActiveImage(organisation?.GetItemId() ?? null, Sax.ImageType.OrganisationLogo))
OrganisationLogoUrl = controller.GetOrganisationLogoLink(organisation.Id, false);
Position = opportunity.Position;
Outline = opportunity.Outline;
Sport = model.GetSportCaption(opportunity.SportIdPrimary);
Region = model.GetRegionCaption(opportunity.RegionIdPrimary);
PublishedOnFmt = model.Localiser.FormatDate(opportunity.PublishedAt);
}
public String Organisation { get; set; }
public String OrganisationLogoUrl { get; set; }
public String Position { get; set; }
public String Outline { get; set; }
public String Sport { get; set; }
public String Region { get; set; }
public String PublishedOnFmt { get; set; }
}
//-------------------------------------------------------------------
// Activities
//-------------------------------------------------------------------
private class AjaxResponse_ActivityList : AjaxResponse_Base
{
public List<ActivityResponseData> Activities { get; } = new List<ActivityResponseData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Activity/Ajax/GetList")]
[Route("Activity/Ajax/GetActivityList")]
[Route("Activity/Ajax/GetActivities")]
public IActionResult Ajax_GetActivityList(Model_Activity model)
{
String fn = GetType().Name + ".Ajax_GetActivityList";
String messageTitle = Localiser["activity.caption.activities"];
AjaxResponse_ActivityList responseData = new AjaxResponse_ActivityList();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
SaxSessionData_Online sessionData = SessionData_Online;
// User context.
SysId userAccountId = SessionUserAccountId;
SaxActivityConsole.IndexContext indexContext = new SaxActivityConsole.IndexContext(userAccountId);
if (SysId.IsValid(userAccountId))
indexContext.UserAccountIdsOwner.Add(userAccountId);
SysId organisationId = model.DecryptId(model.Filter.OrganisationId);
if (SysId.IsValid(organisationId))
indexContext.OrganisationIdsOwner.Add(organisationId);
// Period.
indexContext.EffectiveTo = DateTime.Today;
// Search filter is dissected to extract regions and sports.
String searchFilter = model.Filter.Search.TrimAll();
List<SysId> sportIds = ExtractSearchFilterSports(ref searchFilter);
List<SysId> regionIds = ExtractSearchFilterRegions(ref searchFilter);
// Sport and region combinations.
if (sportIds.IsEmpty())
{
// No sports extractd from search filter - default to user's sports.
foreach (SysId sportId in sessionData.Profile.Sports.Select(item => item.SportId))
{
if (sportIds.Contains(sportId)) continue;
sportIds.AddId(sportId);
// Include child sports.
sportIds.AddIds(model.GetChildSports_Active(sportId).Select(item => item.Id));
model.GetSportCaption(sportId);
}
}
if (regionIds.IsEmpty())
{
// No regions extracted from search filter - default to user's region(s).
regionIds.AddId(sessionData.User.RegionIdProvince);
if (regionIds.IsEmpty())
regionIds.AddId(sessionData.User.RegionIdCountry);
}
foreach (SysId sportId in sportIds)
{
foreach (SysId regionId in regionIds)
indexContext.SportRegionIdsIncluded.Add(new SaxActivityIndex.SportRegionIds(sportId, regionId));
}
// Populate activities.
List<ItemId> activityItemIds = new List<ItemId>();
SaxActivityConsole.IndexResults indexResults = ActivityConsole.QueryIndex(indexContext);
foreach (SaxActivityConsole.IndexResults.ActivityData itemData in indexResults.Items)
{
activityItemIds.Add(itemData.ItemId);
ActivityResponseData activityData = new ActivityResponseData();
activityData.ItemType = itemData.ItemId.Type;
if (itemData.ItemId.Type == (int)Sax.DataItemType.Article)
activityData.ItemTypeClass = "Article";
else if (itemData.ItemId.Type == (int)Sax.DataItemType.OrganisationEvent)
activityData.ItemTypeClass = "Event";
else if (itemData.ItemId.Type == (int)Sax.DataItemType.OrganisationOpportunity)
activityData.ItemTypeClass = "Opportunity";
// ToDo Mask activity index Id with cross-reference stored in session data.
activityData.ItemId = model.EncryptId(itemData.ItemId.Id);
// Organisation.
activityData.Organisation = itemData.Organisation;
// Organisation Logo URL.
if (SysId.IsValid(itemData.OrganisationId) && itemData.OrganisationHasLogo)
activityData.OrganisationLogoUrl = GetOrganisationLogoLink(itemData.OrganisationId, false);
// Author.
activityData.Author = itemData.Author;
// Thumbnail image URL.
if (SysId.IsValid(itemData.ItemImageIdThumbnail))
activityData.ThumbnailUrl = GetItemImageLink(itemData.ItemImageIdThumbnail);
// Activity details.
activityData.Caption = itemData.Caption;
activityData.Blurb = itemData.Blurb;
activityData.Sport = Localiser.GetSportName(itemData.SportId, itemData.SportName);
activityData.Region = Localiser.GetRegionName(itemData.RegionId, itemData.RegionName);
if ((int)itemData.Reaction > 0)
activityData.Reaction = (int)itemData.Reaction;
activityData.PublishedOnFmt = Localiser.FormatDate(itemData.EffectiveOn);
responseData.Activities.Add(activityData);
}
if (!activityItemIds.IsEmpty())
ActivityConsole.RecordUserActivitiesListed(userAccountId, activityItemIds, DbUpdateInfo);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
/// <summary>Extract Sports from activity search filter.</summary>
private List<SysId> ExtractSearchFilterSports(ref string searchFilter)
{
List<SysId> sportIds = new List<SysId>();
if (StringUtil.IsEmpty(searchFilter)) return sportIds;
foreach (SaxSport.Data sport in DataCacheConsole.GetActiveSports())
{
Regex reSport = new Regex("\\b" + Regex.Escape(sport.Name) + "\\b", RegexOptions.IgnoreCase);
Match match = reSport.Match(searchFilter);
if (match.Success)
{
sportIds.Add(sport.Id);
searchFilter = reSport.Replace(searchFilter, " ").TrimAll();
}
}
return sportIds;
}
/// <summary>Extract Regions from activity search filter.</summary>
private List<SysId> ExtractSearchFilterRegions(ref string searchFilter)
{
List<SysId> regionIds = new List<SysId>();
if (StringUtil.IsEmpty(searchFilter)) return regionIds;
foreach (SaxRegion.Data region in DataCacheConsole.GetActiveRegions())
{
Regex reRegion = new Regex("\\b" + Regex.Escape(region.Name) + "\\b", RegexOptions.IgnoreCase);
Match match = reRegion.Match(searchFilter);
if (match.Success)
{
regionIds.Add(region.Id);
searchFilter = reRegion.Replace(searchFilter, " ").TrimAll();
}
}
return regionIds;
}
//-------------------------------------------------------------------
// Activity Info
//-------------------------------------------------------------------
private class AjaxResponse_ActivityInfo : AjaxResponse_Base
{
public ActivityResponseData_Article Article { get; set; }
public ActivityResponseData_Event Event { get; set; }
public ActivityResponseData_Opportunity Opportunity { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Activity/Ajax/GetInfo")]
[Route("Activity/Ajax/GetItemInfo")]
[Route("Activity/Ajax/GetActivityInfo")]
[Route("Activity/Ajax/GetActivity")]
public IActionResult Ajax_GetActivityInfo(Model_Activity model)
{
String fn = GetType().Name + ".Ajax_GetActivityInfo";
String messageTitle = Localiser["activity.caption.activity"];
AjaxResponse_ActivityInfo responseData = new AjaxResponse_ActivityInfo();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
SaxSessionData_Online sessionData = SessionData_Online;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify activity.
Sax.DataItemType itemType = (Sax.DataItemType)StringUtil.ToShort(model.ActivityItemType);
SysId itemId = model.ExtractSysId(model.ActivityItemId);
if (itemType <= 0 || !SysId.IsValid(itemId))
{
SaLogger.Warn(fn + " Activity type/Id inncorrectly specified." + GetLogSuffix());
return Json(responseData);
}
// Retrieve activity details.
if (itemType == Sax.DataItemType.Article)
{
// Article.
SaxArticle article = ActivityMgr.GetArticleById(itemId);
disposableItems.Add(article);
if (article == null)
{
SaLogger.Warn(fn + " Article not found for Activity Id: " + itemId.ToString() + "." + GetLogSuffix());
return Json(responseData);
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(article.OrganisationId);
disposableItems.Add(organisation);
responseData.Article = new ActivityResponseData_Article(this, model, article, organisation);
}
else if (itemType == Sax.DataItemType.OrganisationEvent)
{
// Organisation Event.
SaxOrganisationEvent organisationEvent = ActivityMgr.GetOrganisationEventById(itemId);
disposableItems.Add(organisationEvent);
if (organisationEvent == null)
{
SaLogger.Warn(fn + " Event not found for Activity Id: " + itemId.ToString() + "." + GetLogSuffix());
return Json(responseData);
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationEvent.OrganisationId);
disposableItems.Add(organisation);
responseData.Event = new ActivityResponseData_Event(this, model, organisationEvent, organisation);
}
else if (itemType == Sax.DataItemType.OrganisationOpportunity)
{
// Organisation Opportunity.
SaxOrganisationOpportunity opportunity = ActivityMgr.GetOrganisationOpportunityById(itemId);
disposableItems.Add(opportunity);
if (opportunity == null)
{
SaLogger.Warn(fn + " Opportunity not found for Activity Id: " + itemId.ToString() + "." + GetLogSuffix());
return Json(responseData);
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(opportunity.OrganisationId);
disposableItems.Add(organisation);
responseData.Opportunity = new ActivityResponseData_Opportunity(this, model, opportunity, organisation);
}
// Record activity viewing.
if (!ActivityConsole.RecordUserActivityViewed(SessionUserAccountId, new ItemId((short)itemType, itemId), DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to record Activity Viewing: " + ActivityConsole.ErrorStateMessage + "." + GetLogSuffix());
return Json(responseData);
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Activity Reaction.
//-------------------------------------------------------------------
private class AjaxResponse_ActivityReaction : AjaxResponse_Base
{
public int Reaction { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Activity/Ajax/React")]
[Route("Activity/Ajax/Reaction")]
public IActionResult Ajax_SetActivityReaction(Model_Activity model)
{
String fn = GetType().Name + ".Ajax_SetActivityReaction";
String messageTitle = Localiser["activity.caption.activity"];
AjaxResponse_ActivityReaction responseData = new AjaxResponse_ActivityReaction();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
SaxSessionData_Online sessionData = SessionData_Online;
// Identify activity.
Sax.DataItemType itemType = (Sax.DataItemType)StringUtil.ToShort(model.ActivityItemType);
SysId itemId = model.ExtractSysId(model.ActivityItemId);
if (itemType <= 0 || !SysId.IsValid(itemId))
{
SaLogger.Warn(fn + " Activity type/Id inncorrectly specified." + GetLogSuffix());
return Json(responseData);
}
// Record activity reaction.
if (!ActivityConsole.RecordUserActivityReaction(SessionUserAccountId, new ItemId((short)itemType, itemId), model.GetActivityReaction(), DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to record Activity Reaction: " + ActivityConsole.ErrorStateMessage + "." + GetLogSuffix());
return Json(responseData);
}
// Complete response.
responseData.Reaction = (int)model.GetActivityReaction();
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,135 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
using MailKit;
using Org.BouncyCastle.Asn1.Mozilla;
using Microsoft.Extensions.Primitives;
using Saxonite.Net.Standard.Base.Data;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Ajax Controller - User Config.
//=======================================================================
partial class AccountController
{
//-------------------------------------------------------------------
// Data definitions.
//-------------------------------------------------------------------
// Update User Config.
//-------------------------------------------------------------------
private class AjaxResponse_UserConfigData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserConfigData(SaxModel_Online model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("UserConfig/Ajax/Update")]
public IActionResult UpdateUserConfig(SaxModel_Online model)
{
String fn = GetType().Name + ".UpdateUserConfig";
AjaxResponse_UserConfigData responseData = new AjaxResponse_UserConfigData(model);
String messageTitle = Localiser["caption.user.config"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve User Config.
Dictionary<Sax.UserConfigType, SaxUserConfig> userConfigs = new Dictionary<Sax.UserConfigType, SaxUserConfig>();
// Update user's configuration.
foreach (String key in Request.Form.Keys)
{
StringValues formValue = "";
Request.Form.TryGetValue(key, out formValue);
SaLogger.Debug(fn + " Form data. Key: " + key + ". Value: " + formValue);
if (key.EqualsCI("HideProfileEligibilityInfo"))
{
SaxUserConfig userConfig = RefreshUserConfig(Sax.UserConfigType.Online_Profile, userConfigs);
if (userConfig != null)
{
disposableItems.Add(userConfig);
SessionData_Online.UserConfig.Profile.Eligibility.HideInfo = StringUtil.ToBool(formValue);
userConfig.ConfigData = SaxConfigData.ProfileData.ToXml(SessionData_Online.UserConfig.Profile);
}
}
}
// Store configuration.
foreach (SaxUserConfig userConfig in userConfigs.Values)
{
if (!SysId.IsValid(userConfig.Id))
{
// Add new configuration.
if (!AccountMgr.InsertUserConfig(userConfig, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to insert User Config: " + AccountMgr.ErrorStateMessage);
}
else
{
// Update existing configuration.
if (!AccountMgr.UpdateUserConfig(userConfig, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to update User Config: " + AccountMgr.ErrorStateMessage);
}
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
/// <summary>
/// Retrieve the corresponding User Config if previously stored, otherwise create.
/// </summary>
private SaxUserConfig RefreshUserConfig(Sax.UserConfigType configType, Dictionary<Sax.UserConfigType, SaxUserConfig> userConfigs)
{
// Check if Config is already in cache.
if (userConfigs.ContainsKey(configType)) return userConfigs.GetValueOrDefault(configType, null);
// Retrieve user config.
SaxUserConfig userConfig = AccountMgr.GetUserConfig(SessionUserAccountId, configType);
if (userConfig == null)
{
// Configuration doesn't currently exist.
userConfig = AccountMgr.CreateUserConfig(SessionUserAccountId, configType);
}
else
{
// User Configuration already exists.
// Refresh configuration in case updated elsewhere.
switch (configType)
{
case Sax.UserConfigType.Online_Profile:
SessionData_Online.UserConfig.Profile = SaxConfigData.ProfileData.FromXml(userConfig.ConfigData);
break;
}
}
userConfigs.Add(userConfig.ConfigType, userConfig);
return userConfig;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,782 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
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.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
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 System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Article Controller - Ajax actions.
//=======================================================================
partial class ArticleController
{
//-------------------------------------------------------------------
// Article Content data.
private class ArticleContentData
{
public List<ArticleContentSegmentData> Segments { get; } = new List<ArticleContentSegmentData>();
}
private class ArticleContentSegmentData
{
public ArticleContentSegmentBodyData Body { get; set; }
public ArticleContentSegmentImageData Image { get; set; }
}
private class ArticleContentSegmentBodyData
{
public String Html { get; set; }
}
private class ArticleContentSegmentImageData
{
public int ItemImageId { get; set; }
public int ImageIdx { get; set; }
public String ImageLink { get; set; }
}
//-------------------------------------------------------------------
// Article data.
private class ArticleListData : AjaxResponse_Data.ItemData_Base
{
public String OrganisationId { get; set; }
public String OrganisationName { get; set; }
public String Headline { get; set; }
public String Summary { get; set; }
public class FeedbackData
{
public String Summary { get; set; }
public long? ViewCount { get; set; } = null;
public long? FeedbackCount { get; set; } = null;
public String FeedbackAverage { get; set; }
}
public FeedbackData Feedback { get; set; }
public String RegionCaptionPrimary { get; set; }
public String SportCaptionPrimary { get; set; }
public String PublishedOnFmt { get; set; }
}
private class ArticleItemData : ArticleListData
{
public String RegionIdPrimary { get; set; }
public String SportIdPrimary { get; set; }
public bool? Published { get; set; } = null;
public bool? PublishedInFuture { get; set; } = null;
public String PublishedOn_YYYYMMDD { get; set; }
public String PublishedOn_Calendar { get; set; }
public ArticleContentData Content { get; } = new ArticleContentData();
}
private void PopulateArticleResponseListData(Model_Article model, SaxArticle article, SaxOrganisation organisation, SaxActivityFeedback activityFeedback, ArticleListData data)
{
data.Id = model.EncryptId(article.Id);
data.OrganisationId = SysId.ToString(article.OrganisationId);
data.Headline = article.Headline;
data.Summary = article.Summary;
PopulateFeedbackResponseListData(activityFeedback, data);
data.RegionCaptionPrimary = model.GetRegionCaption(article.RegionIdPrimary);
data.SportCaptionPrimary = model.GetSportCaption(article.SportIdPrimary);
data.CreatedOnFmt = model.FormatDateForDisplay(article.CreateStamp.On);
data.PublishedOnFmt = model.FormatDateForDisplay(article.PublishedAt);
data.StatusCode = article.StatusCode;
data.StatusDesc = article.StatusDesc;
}
private void PopulateArticleResponseItemData(Model_Article model, SaxArticle article, SaxOrganisation organisation, SaxActivityFeedback activityFeedback, ArticleItemData data)
{
PopulateArticleResponseListData(model, article, organisation, activityFeedback, data as ArticleListData);
data.RegionIdPrimary = model.GetEncryptedRegionId(article.RegionIdPrimary);
data.SportIdPrimary = model.GetEncryptedSportId(article.SportIdPrimary);
data.Published = DateUtil.IsValid(article.PublishedAt) ? true : (bool?)null;
data.PublishedInFuture = (data.Published == true && article.PublishedAt != null && ((DateTime)article.PublishedAt).CompareTo(DateTime.Today) >= 0) ? true : (bool?)null;
data.PublishedOn_YYYYMMDD = DateUtil.Format(article.PublishedAt, "yyyyMMdd");
data.PublishedOn_Calendar = model.FormatDateForForm(article.PublishedAt);
// Article content.
SaxArticle.ContentData articleContent = SaxArticle.ExtractContent(article.BodyData);
foreach (var contentSegment in articleContent.Segments)
{
var segmentData = new ArticleContentSegmentData();
if (contentSegment.Body.IsValid())
{
segmentData.Body = new ArticleContentSegmentBodyData();
segmentData.Body.Html = contentSegment.Body.Html;
}
else if (contentSegment.Image.IsValid())
{
segmentData.Image = new ArticleContentSegmentImageData();
segmentData.Image.ItemImageId = SysId.ToInt(contentSegment.Image.ItemImageId);
segmentData.Image.ImageLink = GetItemImageLink(contentSegment.Image.ItemImageId);
}
data.Content.Segments.Add(segmentData);
}
}
// Feedback.
private void PopulateFeedbackResponseData(SaxActivityFeedback activityFeedback, ArticleItemData data)
{
PopulateFeedbackResponseListData(activityFeedback, data);
}
private void PopulateFeedbackResponseListData(SaxActivityFeedback activityFeedback, ArticleListData data)
{
if (activityFeedback == null) return;
PopulateFeedbackResponseListData(activityFeedback.ViewCount, activityFeedback.FeedbackCount, activityFeedback.FeedbackTotal, activityFeedback.FeedbackAverage, data);
}
private void PopulateFeedbackResponseListData(long viewCount, long feedbackCount, long feedbackTotal, decimal feedbackAverage, ArticleListData data)
{
if (viewCount <= 0) return;
data.Feedback = new ArticleListData.FeedbackData();
data.Feedback.Summary = feedbackCount.ToString() + "/" + viewCount.ToString();
if (feedbackCount > 0)
data.Feedback.Summary = feedbackAverage.ToString("N1") + " " + data.Feedback.Summary;
data.Feedback.ViewCount = viewCount;
data.Feedback.FeedbackCount = feedbackCount;
data.Feedback.FeedbackAverage = feedbackAverage.ToString("N1");
}
//-------------------------------------------------------------------
// Get Article List ajax request.
//-------------------------------------------------------------------
private class OrganisationData : AjaxResponse_Data.ItemIdName { }
private class AjaxResponse_ArticleList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ArticleList(SaxModel_Online model) : base(model) { }
// Article list.
public List<ArticleListData> Articles { get; } = new List<ArticleListData>();
/// <summary>Organisations that the user has rights to author articles for.</summary>
public List<OrganisationData> Organisations { get; set; }
public ItemIdName DefaultSport { get; set; }
public ItemIdName DefaultRegion { get; set; }
public List<String> ImageContentTypes { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Article/Ajax/GetList")]
[Route("Article/Ajax/GetArticles")]
public IActionResult Ajax_GetArticleList(Model_Article model)
{
String fn = GetType().Name + ".Ajax_GetArticleList";
String messageTitle = Localiser["article.caption.articles"];
AjaxResponse_ArticleList responseData = new AjaxResponse_ArticleList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
SysId userAccountId = SessionUserAccountId;
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// User account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId);
disposableItems.Add(userAccount);
// Organisations that the user has rights to author articles for.
foreach (SaxAccountMgr.OrganisationIdName articleOrganisation in AccountMgr.GetOrganisationsForAnyUserRole(userAccountId, new[] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.ArticleAdmin }))
{
if (responseData.Organisations == null) responseData.Organisations = new List<OrganisationData>();
OrganisationData organisationData = new OrganisationData()
{
Id = model.EncryptId(articleOrganisation.Id),
Name = articleOrganisation.Name
};
responseData.Organisations.Add(organisationData);
}
// User must either be a journalist or allowed to author organisation articles.
if (!UserIsJournalist && responseData.Organisations == null) return GetAjaxResult_GoHome(responseData);
if (UserIsJournalist && responseData.Organisations != null)
{
OrganisationData organisationData = new OrganisationData()
{
Id = "",
Name = "No Organisation - Journalist Articles"
};
responseData.Organisations.Insert(0, organisationData);
}
// Filter organisation.
SysId organisationIdFilter = model.ExtractSysId(model.Filter.OrganisationId);
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationIdFilter);
disposableItems.Add(organisation);
// Default Sport - defined by organisation if selected.
SysId defaultSportId = organisation?.SportIdPrimary ?? null;
if (SysId.IsValid(defaultSportId))
{
responseData.DefaultSport = new AjaxResponse_Data.ItemIdName()
{
Id = model.GetEncryptedSportId(defaultSportId),
Name = model.GetSportCaption(defaultSportId)
};
}
// Default region - defined by organisation (if selected) otherwise user themselves.
SysId defaultRegionId = organisation?.RegionIdPrimary ?? userAccount?.RegionIdCountry ?? null;
if (SysId.IsValid(defaultRegionId))
{
responseData.DefaultRegion = new AjaxResponse_Data.ItemIdName()
{
Id = model.GetEncryptedRegionId(defaultRegionId),
Name = model.GetRegionCaption(defaultRegionId)
};
}
// Article image content types.
responseData.ImageContentTypes = SaxItemImage.GetValidContentTypes(Sax.DataItemType.Article);
// Item filter.
SaxArticle.Params prmArticle = new SaxArticle.Params();
prmArticle.UserAccountIdsAuthorIncluded.Add(sessionData.User.UserAccountId);
if (SysId.IsValid(organisationIdFilter))
prmArticle.OrganisationIdsIncluded.Add(organisationIdFilter);
else
prmArticle.ExcludeOrganisationArticles = true;
prmArticle.ArticleFilter = model.Filter.Article;
prmArticle.StatusCat = SaxArticle.StatusCat.NotDeleted;
model.Paging.ItemTotal = (ulong)ActivityMgr.GetArticleCount(prmArticle);
model.PopulatePagingTo(prmArticle.ResultsPaging);
// Item columns.
ColumnDfn colId = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Id);
ColumnDfn colOrganisationId = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.OrganisationId);
ColumnDfn colOrganisationName = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Organisation_Name);
ColumnDfn colHeadline = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Headline);
ColumnDfn colSummary = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Summary);
ColumnDfn colRegionIdPrimary = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.RegionIdPrimary);
ColumnDfn colSportIdPrimary = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.SportIdPrimary);
ColumnDfn colPublishedOn = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.PublishedAt_Date);
ColumnDfn colStatus = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Status);
ColumnDfn colCreatedOn = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.CreateOn_Date);
// Feedback.
ColumnDfn colFeedback_ViewCount = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_ViewCount);
ColumnDfn colFeedback_FeedbackCount = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_FeedbackCount);
ColumnDfn colFeedback_FeedbackTotal = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_FeedbackTotal);
ColumnDfn colFeedback_FeedbackAverage = ActivityMgr.GetArticleColumnDfn(SaxArticle.Column.Feedback_FeedbackAverage);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOrganisationId, colOrganisationName, colHeadline, colSummary,
colRegionIdPrimary, colSportIdPrimary, colPublishedOn,
colStatus, colCreatedOn,
colFeedback_ViewCount, colFeedback_FeedbackCount, colFeedback_FeedbackTotal, colFeedback_FeedbackAverage
};
// Sort columns.
colCreatedOn.SortAscending = false;
prmArticle.SortColumns.Add(colCreatedOn);
// Query items.
using (DbResultSet resultSet = ActivityMgr.ExecuteArticleQuery(columns, prmArticle))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
ArticleListData articleData = new ArticleListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
OrganisationId = model.EncryptId(new SysId(values.GetLong(colOrganisationId.DataName))),
OrganisationName = values.GetString(colOrganisationName.DataName),
Headline = values.GetString(colHeadline.DataName),
Summary = values.GetString(colSummary.DataName),
RegionCaptionPrimary = model.GetRegionCaption(SysId.ToSysId(values.GetString(colRegionIdPrimary.DataName))),
SportCaptionPrimary = model.GetSportCaption(SysId.ToSysId(values.GetString(colSportIdPrimary.DataName))),
CreatedOnFmt = model.FormatDateForDisplay(values.GetDateTime(colCreatedOn.DataName)),
PublishedOnFmt = model.FormatDateForDisplay(values.GetDateTime(colPublishedOn.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
// Feedback.
long viewCount = values.GetLong(colFeedback_ViewCount.DataName);
if (viewCount > 0)
PopulateFeedbackResponseListData(viewCount, values.GetLong(colFeedback_FeedbackCount.DataName), values.GetLong(colFeedback_FeedbackTotal.DataName), values.GetDecimal(colFeedback_FeedbackAverage.DataName), articleData);
responseData.Articles.Add(articleData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Articles.Count;
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Article ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Article : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Article(SaxModel_Online model) : base(model) { }
// Article details.
public ArticleItemData Article { get; } = new ArticleItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Article/Ajax/Get")]
[Route("Article/Ajax/GetItem")]
[Route("Article/Ajax/GetArticle")]
public IActionResult Ajax_GetArticle(Model_Article model)
{
String fn = GetType().Name + ".Ajax_GetArticle";
String messageTitle = Localiser["article.caption.article"];
AjaxResponse_Article responseData = new AjaxResponse_Article(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
SysId userAccountId = SessionUserAccountId;
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId articleId = model.ExtractSysId(model.ArticleId);
if (!SysId.IsValid(articleId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxArticle article = ActivityMgr.GetArticleById(articleId);
disposableItems.Add(article);
if (article == null)
{
SaLogger.Warn(fn + " Failed to retrieve Article for Id: '" + model.ArticleId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
if (!article.UserAccountIdAuthor.Equals(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent Article Author. Expected: " + userAccountId + ". Found: " + article.UserAccountIdAuthor);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(article.OrganisationId);
disposableItems.Add(organisation);
// Delete upload images.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.Article, articleId));
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article_Upload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted;
foreach (SaxItemImage itemImage in ItemDataMgr.GetItemImageList(prmItemImage))
{
disposableItems.Add(itemImage);
itemImage.SessionUid = 0;
itemImage.ImageUid = 0;
itemImage.Status = SaStatus.Deleted;
ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo);
}
// Article Feedback.
SaxActivityFeedback activityFeedback = ActivityMgr.GetActivityFeedbackForItem(article.GetItemId());
disposableItems.Add(activityFeedback);
// Construct response.
PopulateArticleResponseItemData(model, article, organisation, activityFeedback, responseData.Article);
}
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Article - Upload Image.
//-------------------------------------------------------------------
private class AjaxResponse_UploadArticleImage : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UploadArticleImage(SaxModel_Online model) : base(model) { }
// Article segment details.
public ArticleContentSegmentData Segment { get; set; }
public String UploadMsg { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Article/Ajax/UploadImage")]
public IActionResult Ajax_UploadArticleImage(Model_Article model)
{
String fn = GetType().Name + ".Ajax_UploadArticleImage";
AjaxResponse_UploadArticleImage responseData = new AjaxResponse_UploadArticleImage(model);
String messageTitle = Localiser["article.caption.article"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate number of images.
if (model.ArticleImage.IsEmpty())
{
SaLogger.Warn(fn + " No image file supplied for upload");
return Json(responseData);
}
if (model.ArticleImage.Count != 1)
{
SaLogger.Warn(fn + " Invalid number of image files uploaded: " + model.ArticleImage.Count);
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.single"]));
}
IFormFile file = model.ArticleImage[0];
// Image properties.
byte[] imageData = null;
using (MemoryStream fileStream = new MemoryStream())
{
file.CopyTo(fileStream);
imageData = fileStream.ToArray();
}
// Validate content type.
if (StringUtil.IsEmpty(file.ContentType) || !SaxItemImage.GetValidContentTypes(Sax.DataItemType.Article).ContainsCI(file.ContentType))
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid.content.type"]));
Sarand.Net.Standard.Base.Core.Sacs.MimeType mimeTypeExpected = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType);
// Validate the image.
ImageUtils.ImageConfig imageConfig = null;
if (mimeTypeExpected == Sarand.Net.Standard.Base.Core.Sacs.MimeType.Svg)
imageConfig = ImageUtils.VerifySvgImageData(imageData);
else
imageConfig = ImageUtils.VerifyImageData(imageData);
if (!imageConfig.ValidImage)
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid"]));
// Validate mime type.
if (!SaxItemImage.GetValidMimeTypes(Sax.DataItemType.Article).Contains(imageConfig.MimeType))
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid.mime"]));
if (imageConfig.MimeType != mimeTypeExpected)
{
SaLogger.Warn($"{SysUtil.Fn} Image upload - inconsistent Mime. Filename: {file.FileName}. Mime: {imageConfig.MimeType}. Expected Mime: {mimeTypeExpected}.");
return Json(responseData.SetControlError("OrganisationLogoImage", Localiser["msg.upload.image.invalid"]));
}
// Resize the image if required.
if (!ImageUtils.ConstrainImage(imageConfig, Sax.Config.Articles.Images.WidthMax, Sax.Config.Articles.Images.HeightMax))
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.resize.failed"]));
// Image belongs to the article.
// If the article doesn't yet exist, it will need to be uploaded to the user first and later transferred.
SysId articleId = model.ExtractSysId(model.ArticleId);
ItemId articleItemId = new ItemId((short)Sax.DataItemType.Article, articleId);
ItemId userItemId = new ItemId((short)Sax.DataItemType.UserAccount, sessionData.User.UserAccountId);
ItemId imageItemId = SysId.IsValid(articleId) ? articleItemId : userItemId;
// Store uploaded image as 'Upload' - awaiting confirmation that the article details are to be saved.
// NB Reuse any existing deleted article upload image to avoid creating new images unless necessary.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
prmItemImage.ItemIdsIncluded.Add(imageItemId);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article_Upload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.DeletedOnly;
prmItemImage.ResultsPaging.PageLength = 1;
prmItemImage.ResultsPaging.PageNo = 1;
List<SaxItemImage> itemImages = ItemDataMgr.GetItemImageList(prmItemImage);
SaxItemImage itemImage = !itemImages.IsEmpty() ? itemImages[0] : null;
if (itemImage == null)
itemImage = ItemDataMgr.CreateItemImage(imageItemId, Sax.ImageType.Article_Upload);
disposableItems.Add(itemImage);
itemImage.ImageType = Sax.ImageType.Article_Upload;
itemImage.Filename = file.FileName;
itemImage.Mime = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType).ToString().ToLower();
itemImage.ContentType = file.ContentType;
itemImage.ImageData = imageConfig.ImageData;
itemImage.Width = imageConfig.Width;
itemImage.Height = imageConfig.Height;
itemImage.SessionUid = sessionData.SessionIdx;
itemImage.ImageUid = sessionData.NextItemIdx;
itemImage.Status = SaxItemImage.DefaultStatus;
if (!SysId.IsValid(itemImage.Id))
{
if (!ItemDataMgr.InsertItemImage(itemImage, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert uploaded image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update uploaded image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.error.item.insert.failed"]));
}
}
// Segment image info.
responseData.Segment = new ArticleContentSegmentData();
responseData.Segment.Image = new ArticleContentSegmentImageData();
responseData.Segment.Image.ImageLink = GetItemImageLink(itemImage.Id);
responseData.Segment.Image.ImageIdx = itemImage.ImageUid;
}
// Complete response.
//responseData.SetNotifyMessage(messageTitle, "Image uploaded.");
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Article ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Article/Ajax/Store")]
[Route("Article/Ajax/StoreItem")]
[Route("Article/Ajax/StoreArticle")]
public IActionResult Ajax_StoreArticle(Model_Article model)
{
String fn = GetType().Name + ".Ajax_StoreArticle";
String messageTitle = Localiser["article.caption.article"];
AjaxResponse_Article responseData = new AjaxResponse_Article(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateArticleData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Validate User & Organisation.
SysId userAccountId = SessionUserAccountId;
SysId organisationId = model.ExtractSysId(model.ArticleOrganisationId);
if (SysId.IsValid(organisationId))
{
// Organisation specified - User must have Article Admin role.
if (!AccountMgr.UserHasAnyOrganisationRole(userAccountId, organisationId, new[] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.ArticleAdmin }))
{
SaLogger.Warn(fn + " User does not have Article Admin role for organisation." + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
else
{
// Organisation not specified - User must be a Journalist.
if (!UserIsJournalist)
{
SaLogger.Warn(fn + " User is not a Journalist for non-organisation article." + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// Validate item Id against 'Exists' flag.
SysId articleId = model.ExtractSysId(model.ArticleId);
bool articleExists = StringUtil.ToBool(model.ArticleExists);
if (articleExists && !SysId.IsValid(articleId))
{
SaLogger.Warn(fn + " Id not specified for existing Article.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!articleExists && SysId.IsValid(articleId))
{
SaLogger.Warn(fn + " Id specified for new Article: '" + model.ArticleId + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxArticle article = ActivityMgr.GetArticleById(articleId);
disposableItems.Add(article);
if (article == null && SysId.IsValid(articleId))
{
SaLogger.Warn(fn + " Failed to retrieve article for Id: '" + model.ArticleId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
if (article != null)
{
// Validate existing article.
if (SysId.ToLong(article.UserAccountIdAuthor) != SysId.ToLong(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent Article Author." + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (SysId.ToLong(article.OrganisationId) != SysId.ToLong(organisationId))
{
SaLogger.Warn(fn + " Inconsistent Article Organisation." + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// Organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
// Create new item if required.
if (article == null)
{
article = ActivityMgr.CreateArticle(sessionData.User.UserAccountId, organisationId);
disposableItems.Add(article);
}
// Populate item properties.
if (model.ArticleRegionId != null)
article.RegionIdPrimary = model.ExtractSysId(model.ArticleRegionId);
if (model.ArticleSportId != null)
article.SportIdPrimary = model.ExtractSysId(model.ArticleSportId);
if (model.ArticleHeadline != null)
article.Headline = model.ArticleHeadline;
if (model.ArticleSummary != null)
article.Summary = model.ArticleSummary;
if (!model.ArticlePublished.ToBool())
{
if (model.ArticlePublish)
article.PublishedAt = model.GetArticlePublishOn();
else
article.PublishedAt = null;
}
if (model.ArticleStatusCode != null)
article.Status = model.ArticleStatus;
// Retrieve all article images - before new article is created as uploaded images are assigned to user first.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
if (SysId.IsValid(article.Id))
prmItemImage.ItemIdsIncluded.Add(article.GetItemId());
else
prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.UserAccount, sessionData.User.UserAccountId));
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article_Upload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted;
List<SaxItemImage> itemImages = ItemDataMgr.GetItemImageList(prmItemImage);
disposableItems.AddRange(itemImages);
// If Article doesn't yet exist, create it now so that images can be assigned.
bool articleCreated = false;
if (!SysId.IsValid(article.Id))
{
if (!ActivityMgr.InsertArticle(article, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert article: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
articleCreated = true;
}
// Content.
if (model.ArticleContentSegmentBodies != null)
{
SaxArticle.ContentData articleContent = new SaxArticle.ContentData();
for (int segmentIdx = 0; segmentIdx < model.ArticleContentSegmentBodies.Count; segmentIdx++)
{
String segmentBody = model.ArticleContentSegmentBodies[segmentIdx];
String segmentItemImageId = model.ArticleContentSegmentItemImageIds.Count > segmentIdx ? model.ArticleContentSegmentItemImageIds[segmentIdx] : "";
String segmentImageIdx = model.ArticleContentSegmentImageIdxs.Count > segmentIdx ? model.ArticleContentSegmentImageIdxs[segmentIdx] : "";
if (!StringUtil.IsEmpty(segmentBody))
{
// HTML Body segment.
var segment = articleContent.AddSegment();
segment.Body.Html = segmentBody;
}
else if (!StringUtil.IsEmpty(segmentItemImageId) || !StringUtil.IsEmpty(segmentImageIdx))
{
SysId itemImageId = model.ExtractSysId(segmentItemImageId);
int imageIdx = StringUtil.ToInt(segmentImageIdx);
// Check for new image first.
SaxItemImage itemImage = itemImages.GetItemImageBySessionImage(sessionData.SessionIdx, imageIdx);
if (itemImage == null)
itemImage = itemImages.GetItemImageById(itemImageId);
if (itemImage != null)
{
// Ensure that the image is correctly identified for the Article.
itemImage.ItemId = article.GetItemId();
itemImage.ImageType = Sax.ImageType.Article;
itemImage.SessionUid = 0;
itemImage.ImageUid = 0;
itemImage.Status = SaxItemImage.DefaultStatus;
if (ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo))
{
// Assign the image segment to the article.
var segment = articleContent.AddSegment();
segment.Image.ItemImageId = itemImage.Id;
// Remove the image from the list as it's now been handled.
itemImages.Remove(itemImage);
}
else
SaLogger.Warn(fn + " Failed to update article image: " + ItemDataMgr.ErrorStateMessage);
}
else
SaLogger.Warn(fn + " Failed to identify article image. ItemImageId: " + itemImageId.ToInt() + ". SessionIdx: " + sessionData.SessionIdx + ". ImageIdx: " + imageIdx + ".");
}
}
article.BodyData = SaxArticle.GetContentData(articleContent);
}
// Assign/Validate thumbnail image.
// Compile list of images in the article body and make a note of the first image.
SysId itemIdImage_First = null;
List<SysId> itemImageIds = new List<SysId>();
foreach (var contentSegment in SaxArticle.ExtractContent(article.BodyData).Segments)
{
var segmentData = new ArticleContentSegmentData();
if (contentSegment.Image.IsValid())
{
if (!SysId.IsValid(itemIdImage_First))
itemIdImage_First = contentSegment.Image.ItemImageId;
itemImageIds.AddId(contentSegment.Image.ItemImageId);
}
}
// If the thumbnail isn't found in the body clear it.
if (!itemImageIds.Contains(article.ItemImageIdThumbnail))
article.ItemImageIdThumbnail = null;
// If the thumbnail isn't assigned, assign the first image.
if (!SysId.IsValid(article.ItemImageIdThumbnail))
article.ItemImageIdThumbnail = itemIdImage_First;
// Update the Article.
if (!ActivityMgr.UpdateArticle(article, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update article [Id:" + SysId.ToString(article.Id) + "]: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
SaLogger.Info(fn + " Article " + (articleCreated ? "created" : "updated") + ". Id: " + article.Id.ToString() + ". User: " + SessionData.User.EmailAddress, true);
// Store Article Activity Index data.
ActivityConsole.StoreActivityIndex(article, DbUpdateInfo);
// Delete remaining images as they are no longer required.
foreach (SaxItemImage itemImage in itemImages)
{
itemImage.SessionUid = 0;
itemImage.ImageUid = 0;
itemImage.Status = SaStatus.Deleted;
ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo);
}
// Article Feedback.
SaxActivityFeedback activityFeedback = ActivityMgr.GetActivityFeedbackForItem(article.GetItemId());
disposableItems.Add(activityFeedback);
// Construct response.
PopulateArticleResponseItemData(model, article, organisation, activityFeedback, responseData.Article);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.Net.Standard.Base.Bus;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Mgr;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Journalist Controller.
//=======================================================================
public partial class ArticleController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public ArticleController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// User must either be a Journalist or have Article Admin rights with at least one organisation.
List<SaxAccountMgr.OrganisationIdName> organisations = AccountMgr.GetOrganisationsForAnyUserRole(SessionUserAccountId, new [] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.ArticleAdmin });
if (organisations.Count < 1 && !UserIsJournalist)
return RedirectToAction("", "");
// Model.
Model_Article model = new Model_Article(Localiser);
model.UserCanAuthorOrganisationArticles = organisations.Count > 0;
model.UserCanAuthorJournalistArticles = UserIsJournalist;
if (organisations.Count > 0)
{
// Default filter to first organisation.
model.Filter.OrganisationId = model.EncryptId(organisations[0].Id);
model.Filter.OrganisationCaption = organisations[0].Name;
}
model.Paging.PageNo = 1;
model.Paging.PageLength = 6;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,103 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Sarand.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
public class HomeController : SaxController_Online
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public HomeController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
String fn = GetType().Name + ".Index";
// Check for 'Keep Logged In' cookie.
// NB Only allowed if there isn't a current user session.
if (!ValidUserSession && AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
{
String encryptedParams = GetRequestCookie(KeepLoggedIn.CookieName);
KeepLoggedInParams keepLoggedInParams = ExtractKeepLoggedInParams(encryptedParams);
String logSuffix = keepLoggedInParams.GetValidParams().Concat("", " ", ".");
if (SysId.IsValid(keepLoggedInParams.UserAccountId))
{
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(keepLoggedInParams.UserAccountId);
if (userAccount != null)
{
disposableItems.Add(userAccount);
if (userAccount.IsStatusActive)
{
List<SaxUserRole> userRoles = AccountMgr.GetRolesForUser(userAccount.Id);
disposableItems.AddRange(userRoles);
List<SaxUserConfig> userConfigs = AccountMgr.GetConfigsForUser(userAccount.Id);
disposableItems.AddRange(userConfigs);
String errorMessage = "";
if (SignUserIn(userAccount, userRoles, userConfigs, true, ref errorMessage))
SaLogger.Info(fn + " Keep Logged In cookie found." + logSuffix);
else SaLogger.Warn(fn + " Keep Logged In cookie found but failed to sign user in: " + errorMessage + "." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount is not active." + logSuffix);
}
else SaLogger.Warn(fn + " Keep Logged In cookie found but UserAccount not found." + logSuffix);
}
}
}
// Redirect to sign-in if not signed-in.
if (!ValidUserSession)
{
ClearKeepLoggedInCookie();
//return RedirectToAction("", "Security");
}
// Home page.
Model_Home model = new Model_Home(Localiser);
model.ProhibitCache = false;
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Privacy()
{
Model_Home model = new Model_Home(Localiser);
return View(model);
}
//-------------------------------------------------------------------
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel(Localiser) { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,229 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Standard.Base.App;
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.Helpers;
using System.IO;
using System.Drawing;
using Sarand.Net.Standard.Base.Core;
using Microsoft.Net.Http.Headers;
using System.Collections.Specialized;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Image Controller - delivers dynamic images.
//=======================================================================
public partial class ImageController : SaxController_Online
{
//-------------------------------------------------------------------
// Data members.
public static String ParamName_OrganisationLogoEncrypted { get; } = "ole";
public static String ParamName_ItemImageEncrypted { get; } = "iie";
public static String ParamName_OrganisationId { get; } = "ori";
public static String ParamName_ItemImageId { get; } = "iii";
//-------------------------------------------------------------------
// Construction.
public ImageController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
// Account Contact image - General
//-------------------------------------------------------------------
// Account Contact Image request.
/// <summary>
/// Get the Account Contact Image for the specified account.
/// <para>Current User Account is used if no other specified in the encrypted parameter.</para>
/// <para>Encrypted parameter:</para>
/// <para>- uaie: User Account Id encrypted.</para>
/// </summary>
[HttpGet]
[Route("Image/Account/Contact")]
public IActionResult GetAccountContactImage(String uaie)
{
String userAccountIdParam = "";// ToDo Decrypt from uaie value.
SysId userAccountId = SysId.ToSysId(userAccountIdParam);
if (!SysId.IsValid(userAccountId))
userAccountId = SessionUserAccountId;
return GetAccountContactImage(userAccountId, Sax.ImageType.Avatar);
}
//-------------------------------------------------------------------
// Account Contact image - Profile
//-------------------------------------------------------------------
// Account Profile Contact Image request.
/// <summary>
/// Get the current Account Profile Contact Image for the specified account.
/// <para>Account is always current User Account - this request is only fir use from the user's own profile page.</para>
/// <para>'Uploaded' avatar image will take precedence as this will be the latest if present.</para>
/// <para>NB Avoid caching this image as it will change whenever a new one is uploaded.</para>
/// </summary>
[HttpGet]
[Route("Image/Account/Contact/Profile")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public IActionResult GetAccountContactImage()
{
// Response should not be cached.
Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate, max-age=0";
Response.Headers[HeaderNames.Expires] = "-1";
SysId userAccountId = SessionUserAccountId;
return GetAccountContactImage(userAccountId, Sax.ImageType.Avatar_Upload);
}
//-------------------------------------------------------------------
/// <summary>
/// Get the
/// </summary>
/// <param name="imageTypePriority"></param>
/// <returns></returns>
private IActionResult GetAccountContactImage(SysId userAccountId, Sax.ImageType imageTypePriority)
{
String fn = GetType().Name + ".GetAccountContactImage";
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return Forbid();
ItemId userItemId = new ItemId((short)Sax.DataItemType.UserAccount, userAccountId);
// Retrieve account contact images.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
prmItemImage.ItemIdsIncluded.Add(userItemId);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Avatar);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Avatar_Upload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.ActiveOnly;
SaxItemImage itemImage = null;
foreach (SaxItemImage itemImageCandidate in ItemDataMgr.GetItemImageList(prmItemImage))
{
// One of the 'Avatar' image types will take precedence.
if (itemImage == null || itemImageCandidate.ImageType == imageTypePriority)
itemImage = itemImageCandidate;
}
if (itemImage == null)
return NotFound();
// Construct response.
return File(itemImage.ImageData, itemImage.ContentType);
}
//-------------------------------------------------------------------
// Organisation logo.
//-------------------------------------------------------------------
/// <summary>
/// Get the logo for the specified Organisation.
/// <para>Method parameter name (ole) must match name of encrypted parameter.</para>
/// <para>Encrypted parameters:</para>
/// <para>- ori: Organisation Id.</para>
/// </summary>
[HttpGet]
[Route("Image/Organisation/Logo")]
public IActionResult GetOrganisationLogo(String ole)
{
return GetOrganisationLogo(ole, false);
}
/// <summary>
/// Get the logo for the specified Organisation.
/// <para>Method parameter name (ole) must match name of encrypted parameter.</para>
/// <para>Encrypted parameters:</para>
/// <para>- ori: Organisation Id.</para>
/// </summary>
[HttpGet]
[Route("Image/Organisation/Logo/Upload")]
public IActionResult GetOrganisationLogoUpload(String ole)
{
return GetOrganisationLogo(ole, true);
}
private IActionResult GetOrganisationLogo(String encryptedParams, bool allowUploadImage)
{
String fn = GetType().Name + ".GetOrganisationLogo";
// Validate session and referrer.
if (!ValidReferer || !ValidUserSession) return Forbid();
// Extract encrypted parameters.
NameValueCollection parameters = ExtractOnlineParams(encryptedParams);
if (parameters.Count < 1) return Forbid();
// Organisation Id.
SysId organisationId = SysId.ToSysId(parameters.Get(ParamName_OrganisationId));
if (!SysId.IsValid(organisationId)) return Forbid();
ItemId organisationItemId = new ItemId((short)Sax.DataItemType.Organisation, organisationId);
// Query Organisation Logo images.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
prmItemImage.ItemIdsIncluded.Add(organisationItemId);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.OrganisationLogo);
if (allowUploadImage)
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.OrganisationLogo_Upload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.ActiveOnly;
SaxItemImage itemImage = null;
foreach (SaxItemImage itemImageCandidate in ItemDataMgr.GetItemImageList(prmItemImage))
{
// The 'Upload' image will take precedence if allowed.
if (itemImage == null || itemImageCandidate.ImageType == Sax.ImageType.OrganisationLogo_Upload)
itemImage = itemImageCandidate;
}
if (itemImage == null)
return NotFound();
// Construct response.
Response.Headers[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate, max-age=0";
Response.Headers[HeaderNames.Expires] = "-1";
return File(itemImage.ImageData, itemImage.ContentType);
}
//-------------------------------------------------------------------
// Item image
//-------------------------------------------------------------------
/// <summary>
/// Get the specified Item Image.
/// <para>Method parameter name (iie) must match name of encrypted parameter.</para>
/// <para>Encrypted parameters:</para>
/// <para>- iii: item Image Id.</para>
/// </summary>
[HttpGet]
[Route("Image/Item")]
public IActionResult GetItemImage(String iie)
{
String fn = GetType().Name + ".GetItemImage";
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return Forbid();
ItemId userItemId = new ItemId((short)Sax.DataItemType.UserAccount, SessionUserAccountId);
// Extract encrypted parameters.
NameValueCollection parameters = ExtractOnlineParams(iie);
if (parameters.Count<1) return Forbid();
// Item Image.
SysId itemImageId = SysId.ToSysId(parameters.Get(ParamName_ItemImageId));
if (!SysId.IsValid(itemImageId)) return Forbid();
SaxItemImage itemImage = ItemDataMgr.GetItemImageById(itemImageId);
if (itemImage == null)
return NotFound();
// Construct response.
return File(itemImage.ImageData, itemImage.ContentType);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,520 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Diagnostics;
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.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Organisation Controller - Events - Ajax actions.
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Organisation Event data.
private class EventListData : AjaxResponse_Data.ItemData_Base
{
public String EventAtDateFmt { get; set; }
public String EventAtTimeFmt { get; set; }
public String EventToDateFmt { get; set; }
public String EventToTimeFmt { get; set; }
public String Duration { get; set; }
public String TypeCode { get; set; }
public String TypeDesc { get; set; }
public String Summary { get; set; }
public String SportCaptionPrimary { get; set; }
public String RegionCaptionCountry { get; set; }
public String PublishedOnFmt { get; set; }
public String ExpiresOnFmt { get; set; }
}
private class EventItemData : EventListData
{
public String EventAt_Calendar { get; set; }
public String EventAt_YYYYMMDD { get; set; }
public String EventAt_HHMM { get; set; }
public String EventTo_Calendar { get; set; }
public String EventTo_YYYYMMDD { get; set; }
public String EventTo_HHMM { get; set; }
public String Details { get; set; }
public String SportIdPrimary { get; set; }
public String RegionIdCountry { get; set; }
public String PublishedOn_YYYYMMDD { get; set; }
public String ExpiresOn_YYYYMMDD { get; set; }
}
private void PopulateEventResponseListData(Model_Organisation_Events model, SaxOrganisationEvent organisationEvent, EventListData data)
{
data.Id = model.EncryptId(organisationEvent.Id);
// 'At' date/time.
data.EventAtDateFmt = Localiser.FormatDate(organisationEvent.EventAt);
if (organisationEvent.HasValidEventAtTime())
data.EventAtTimeFmt = DateUtil.Format(organisationEvent.EventAt, "HH:mm");
// 'To' date/time.
data.EventToDateFmt = Localiser.FormatDate(organisationEvent.EventTo);
if (organisationEvent.HasValidEventToTime())
data.EventToTimeFmt = DateUtil.Format(organisationEvent.EventTo, "HH:mm");
// Event properties.
if (DateUtil.IsValid(organisationEvent.EventTo) && organisationEvent.EventTo > organisationEvent.EventAt)
data.Duration = DateUtil.FormatNumberOfSeconds(((DateTime)organisationEvent.EventTo - organisationEvent.EventAt).TotalSeconds);
data.TypeCode = EventTypeCodes.GetCodeForType(organisationEvent.EventType);
data.TypeDesc = Localiser.GetEventTypeCaption(organisationEvent.EventType);
data.Summary = organisationEvent.Summary;
data.SportCaptionPrimary = model.GetSportCaption(organisationEvent.SportIdPrimary);
data.RegionCaptionCountry = model.GetRegionCaption(organisationEvent.RegionIdCountry);
data.CreatedOnFmt = Localiser.FormatDate(organisationEvent.CreateStamp.On);
if (DateUtil.IsValid(organisationEvent.PublishedAt))
data.PublishedOnFmt = Localiser.FormatDate(organisationEvent.PublishedAt);
if (DateUtil.IsValid(organisationEvent.ExpiresAt))
data.ExpiresOnFmt = Localiser.FormatDate(organisationEvent.ExpiresAt);
data.StatusCode = organisationEvent.StatusCode;
data.StatusDesc = organisationEvent.StatusDesc;
}
private void PopulateEventResponseItemData(Model_Organisation_Events model, SaxOrganisationEvent organisationEvent, EventItemData data)
{
PopulateEventResponseListData(model, organisationEvent, data as EventListData);
// 'At' date/time.
data.EventAt_Calendar = model.FormatDateForForm(organisationEvent.EventAt);
data.EventAt_YYYYMMDD = DateUtil.Format(organisationEvent.EventAt, "yyyyMMdd");
if (organisationEvent.HasValidEventAtTime())
data.EventAt_HHMM = DateUtil.Format(organisationEvent.EventAt, "HH:mm");
// 'To' date/time.
data.EventTo_Calendar = model.FormatDateForForm(organisationEvent.EventTo);
data.EventTo_YYYYMMDD = DateUtil.Format(organisationEvent.EventTo, "yyyyMMdd");
if (organisationEvent.HasValidEventToTime())
data.EventTo_HHMM = DateUtil.Format(organisationEvent.EventTo, "HH:mm");
// Event properties.
data.Details = organisationEvent.Details;
data.RegionIdCountry = model.GetEncryptedRegionId(organisationEvent.RegionIdCountry);
data.SportIdPrimary = model.GetEncryptedSportId(organisationEvent.SportIdPrimary);
if (DateUtil.IsValid(organisationEvent.PublishedAt))
data.PublishedOn_YYYYMMDD = DateUtil.Format(organisationEvent.PublishedAt, "yyyyMMdd");
if (DateUtil.IsValid(organisationEvent.ExpiresAt))
data.ExpiresOn_YYYYMMDD = DateUtil.Format(organisationEvent.ExpiresAt, "yyyyMMdd");
}
//-------------------------------------------------------------------
// Get Event List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_EventList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_EventList(SaxModel_Online model) : base(model) { }
// Event list.
public List<EventListData> Events { get; } = new List<EventListData>();
// User profile.
public class UserData
{
public bool CanManage { get; set; }
}
public UserData User { get; } = new UserData();
// Organisation.
public class OrganisationData
{
public String Id { get; set; }
public String Name { get; set; }
public String SportId { get; set; }
public String SportName { get; set; }
public String RegionId { get; set; }
public String RegionName { get; set; }
}
public OrganisationData Organisation { get; } = new OrganisationData();
// Reference data.
public List<CodeDesc> EventTypes { get; } = new List<CodeDesc>();
public String EventTypeDefault { get; set; }
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetEventList")]
[Route("Organisation/Ajax/GetEvents")]
public IActionResult Ajax_GetEventList(Model_Organisation_Events model)
{
String fn = GetType().Name + ".Ajax_GetEventList";
String messageTitle = Localiser["organisation.caption.events"];
AjaxResponse_EventList responseData = new AjaxResponse_EventList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
SysId userAccountId = SessionUserAccountId;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Organisation.
SysId organisationId = model.ExtractSysId(model.Filter.OrganisationId);
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Event list. OrganisationId: '" + model.Filter.OrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (organisation != null)
{
// Store filter data.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
// Reference data - organisationEvent types.
foreach (TypeCodeDesc eventType in EventTypeCodes)
responseData.EventTypes.Add(eventType.Code, Localiser.GetEventTypeCaption((Sax.EventType)eventType.Type));
// ToDo: Organisation default Event Type.
responseData.EventTypeDefault = EventTypeCodes.GetCodeForType(Sax.EventType.Playing);
// User organisation.
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(userAccountId);
prmUserOrganisation.OrganisationIdsIncluded.Add(organisationId);
prmUserOrganisation.UserAccountStatusCat = SaxUserAccount.StatusCat.ActiveOnly;
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisation(prmUserOrganisation);
disposableItems.Add(userOrganisation);
if (userOrganisation == null)
{
SaLogger.Warn(fn + " Invalid/inactive user organisation for Event list. UserAccountId: " + SysId.ToString(userAccountId) + ". OrganisationId: " + model.Filter.OrganisationId + ".");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Organisation.
responseData.Organisation.Id = SysId.ToString(organisation.Id);
responseData.Organisation.Name = organisation.Name;
responseData.Organisation.SportId = model.GetEncryptedSportId(organisation.SportIdPrimary);
responseData.Organisation.SportName = model.GetSportCaption(organisation.SportIdPrimary);
responseData.Organisation.RegionId = model.GetEncryptedRegionId(organisation.RegionIdPrimary);
responseData.Organisation.RegionName = model.GetRegionCaption(organisation.RegionIdPrimary);
// User details.
responseData.User.CanManage = userOrganisation.CanManageSchedule;
// Item filter.
SaxOrganisationEvent.Params prmEvent = new SaxOrganisationEvent.Params();
prmEvent.UserAccountIdsIncluded.Add(userAccountId);
prmEvent.OrganisationIdsIncluded.Add(organisationId);
switch (model.Filter.Period.PeriodType)
{
case Model_Organisation_Events.PeriodType.FromThisMonth:
prmEvent.EventFrom = DateUtil.StartOfMonth(DateTime.Today);
break;
case Model_Organisation_Events.PeriodType.FromLastMonth:
prmEvent.EventFrom = DateUtil.StartOfMonth(DateUtil.IncrementMonth(DateTime.Today, -1));
break;
case Model_Organisation_Events.PeriodType.FromNextMonth:
prmEvent.EventFrom = DateUtil.StartOfMonth(DateUtil.IncrementMonth(DateTime.Today, 1));
break;
case Model_Organisation_Events.PeriodType.FromThisWeek:
default:
prmEvent.EventFrom = DateUtil.StartOfWorkingWeek(DateTime.Today);
break;
}
prmEvent.EventFilter = model.Filter.Event;
prmEvent.StatusCat = SaxOrganisationEvent.StatusCat.NotDeleted;
prmEvent.SortColumns.Add(ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.EventAt_DateTime));
model.Paging.ItemTotal = (ulong)ActivityMgr.GetOrganisationEventCount(prmEvent);
model.PopulatePagingTo(prmEvent.ResultsPaging);
// Item columns.
ColumnDfn colId = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Id);
ColumnDfn colEventAt = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.EventAt_DateTime);
ColumnDfn colEventTo = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.EventTo_DateTime);
ColumnDfn colEventTypeCode = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.EventTypeCode);
ColumnDfn colSummary = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Summary);
ColumnDfn colRegionId = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.RegionIdCountry);
ColumnDfn colRegionName = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Region_Name);
ColumnDfn colSportId = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.SportIdPrimary);
ColumnDfn colSportName = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Sport_Name);
ColumnDfn colPublishedOn = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.PublishedAt_Date);
ColumnDfn colExpiresOn = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.ExpiresAt_Date);
ColumnDfn colStatus = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.Status);
ColumnDfn colCreatedOn = ActivityMgr.GetOrganisationEventColumnDfn(SaxOrganisationEvent.Column.CreateOn_Date);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colEventAt, colEventTo, colEventTypeCode, colSummary,
colRegionId, colRegionName, colSportId, colSportName,
colPublishedOn, colExpiresOn,
colStatus, colCreatedOn
};
// Sort columns.
colCreatedOn.SortAscending = false;
prmEvent.SortColumns.Add(colCreatedOn);
// Query items.
using (DbResultSet resultSet = ActivityMgr.ExecuteOrganisationEventQuery(columns, prmEvent))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
DateTime eventAt = values.GetDateTime(colEventAt.DataName);
DateTime? eventTo = values.GetDateTime(colEventTo.DataName);
EventListData eventData = new EventListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
EventAtDateFmt = Localiser.FormatDate(values.GetDateTime(colEventAt.DataName)),
TypeCode = values.GetString(colEventTypeCode.DataName),
TypeDesc = Localiser.GetEventTypeCaption((Sax.EventType)EventTypeCodes.GetTypeForCode(values.GetString(colEventTypeCode.DataName))),
Summary = ActivityMgr.GetOrganisationEventDataValue(colSummary, values).AsString(),
RegionCaptionCountry = model.GetRegionCaption(SysId.ToSysId(values.GetString(colRegionId.DataName))),
SportCaptionPrimary = model.GetSportCaption(SysId.ToSysId(values.GetString(colSportId.DataName))),
CreatedOnFmt = Localiser.FormatDate(values.GetDateTime(colCreatedOn.DataName)),
PublishedOnFmt = Localiser.FormatDate(values.GetDateTime(colPublishedOn.DataName)),
ExpiresOnFmt = Localiser.FormatDate(values.GetDateTime(colExpiresOn.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
if (SaxOrganisationEvent.HasValidEventTime(eventAt))
eventData.EventAtTimeFmt = DateUtil.Format(eventAt, "HH:mm");
if (DateUtil.IsValid(eventTo) && eventAt.Date != ((DateTime)eventTo).Date)
eventData.EventToDateFmt = Localiser.FormatDate(eventTo);
if (SaxOrganisationEvent.HasValidEventTime(eventTo))
eventData.EventToTimeFmt = DateUtil.Format(eventTo, "HH:mm");
if (DateUtil.IsValid(eventTo) && eventTo > eventAt)
eventData.EventAtTimeFmt = DateUtil.FormatNumberOfSeconds(((DateTime)eventTo - eventAt).TotalSeconds);
responseData.Events.Add(eventData);
}
}
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Events.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Event ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Event : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Event(SaxModel_Online model) : base(model) { }
// Event details.
public EventItemData Event { get; } = new EventItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetEvent")]
public IActionResult Ajax_GetEvent(Model_Organisation_Events model)
{
String fn = GetType().Name + ".Ajax_GetEvent";
String messageTitle = Localiser["organisation.caption.event"];
AjaxResponse_Event responseData = new AjaxResponse_Event(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId eventId = model.ExtractSysId(model.EventId);
if (!SysId.IsValid(eventId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxOrganisationEvent organisationEvent = ActivityMgr.GetOrganisationEventById(eventId);
disposableItems.Add(organisationEvent);
if (organisationEvent == null)
{
SaLogger.Warn(fn + " Failed to retrieve Organisation Event for Id: '" + model.EventId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationEvent.OrganisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Event. OrganisationId: '" + organisationEvent.OrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Make a note of the selected Organisation.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
// Construct response.
PopulateEventResponseItemData(model, organisationEvent, responseData.Event);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Event ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/StoreEvent")]
public IActionResult Ajax_StoreEvent(Model_Organisation_Events model)
{
String fn = GetType().Name + ".Ajax_StoreEvent";
String messageTitle = Localiser["organisation.caption.event"];
AjaxResponse_Event responseData = new AjaxResponse_Event(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateEventData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Validate Event against Id against 'Exists' flag.
SysId eventId = model.ExtractSysId(model.EventId);
bool eventExists = StringUtil.ToBool(model.EventExists);
if (eventExists && !SysId.IsValid(eventId))
{
SaLogger.Warn(fn + " Id not specified for existing Event.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!eventExists && SysId.IsValid(eventId))
{
SaLogger.Warn(fn + " Id specified for new Event: '" + model.EventId + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxOrganisationEvent organisationEvent = ActivityMgr.GetOrganisationEventById(eventId);
disposableItems.Add(organisationEvent);
if (organisationEvent == null && SysId.IsValid(eventId))
{
SaLogger.Warn(fn + " Failed to retrieve organisationEvent for Id: '" + model.EventId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate the Organisation.
SysId organisationId = model.ExtractSysId(model.EventOrganisationId);
if (!SysId.IsValid(organisationId))
{
SaLogger.Warn(fn + " Organisation Id not specified for Event.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Event. OrganisationId: '" + model.EventOrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (organisationEvent != null && !organisationEvent.OrganisationId.Equals(organisationId))
{
SaLogger.Warn(fn + " Inconsistent Organisation Id specified for Event. Expected: " + organisationEvent.OrganisationId + ". Found: " + organisationId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Ensure that the user has the correct role to update Events for this organisation.
if (!AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisationId, new[] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.ScheduleAdmin }))
{
SaLogger.Warn(fn + " User doesn't have 'Event Admin' role for Organisation Id: " + organisationId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Create new item if required.
if (organisationEvent == null)
{
organisationEvent = ActivityMgr.CreateOrganisationEvent(organisationId);
disposableItems.Add(organisationEvent);
}
// Populate item properties.
if (model.EventSportIdPrimary != null)
organisationEvent.SportIdPrimary = model.ExtractSysId(model.EventSportIdPrimary);
if (model.EventRegionIdCountry != null)
organisationEvent.RegionIdCountry = model.ExtractSysId(model.EventRegionIdCountry);
if (model.EventAtDate != null)
{
// 'At' date.
organisationEvent.EventAt = model.ParseDateFromForm(model.EventAtDate);
if (model.EventAtTimeSet && !StringUtil.IsBlank(model.EventAtTime))
{
// For midnight (00:00) set +1 minute so that the value is treated as a valid start time.
if (model.EventAtTime.Equals("00:00"))
model.EventAtTime = "00:01";
organisationEvent.EventAt = DateUtil.SetTime(organisationEvent.EventAt, DateUtil.ParseExact(model.EventAtTime, "HH:mm"));
}
if (model.EventToDateSet || model.EventToTimeSet)
{
// 'To' date.
organisationEvent.EventTo = organisationEvent.EventAt;
if (model.EventToDate != null && model.EventToDateSet)
organisationEvent.EventTo = model.ParseDateFromForm(model.EventToDate);
// 'To' time.
if (model.EventAtTimeSet && model.EventToTimeSet && !StringUtil.IsBlank(model.EventToTime))
{
// For midnight (00:00) set +1 minute so that the value is treated as a valid finish time.
if (model.EventToTime.Equals("00:00"))
model.EventToTime = "00:01";
organisationEvent.EventTo = DateUtil.SetTime(organisationEvent.EventTo ?? organisationEvent.EventAt, DateUtil.ParseExact(model.EventToTime, "HH:mm"));
}
}
else organisationEvent.EventTo = null;
}
if (model.EventTypeCode != null)
organisationEvent.EventType = (Sax.EventType)EventTypeCodes.GetTypeForCode(model.EventTypeCode);
if (model.EventSummary != null)
organisationEvent.Summary = model.EventSummary;
if (model.EventDetails != null)
organisationEvent.Details = model.EventDetails;
if (model.EventStatusCode != null)
organisationEvent.Status = model.EventStatus;
// Store item.
bool eventCreated = false;
if (!SysId.IsValid(organisationEvent.Id))
{
if (!ActivityMgr.InsertOrganisationEvent(organisationEvent, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert organisationEvent: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
eventCreated = true;
}
else
{
if (!ActivityMgr.UpdateOrganisationEvent(organisationEvent, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update organisationEvent [Id:" + SysId.ToString(organisationEvent.Id) + "]: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
SaLogger.Info(fn + " Event " + (eventCreated ? "created" : "updated") + ". Id: " + organisationEvent.Id.ToString() + ". User: " + SessionData.User.EmailAddress + ". Organisation: " + organisation.Name, true);
// Store Event Activity Index data.
ActivityConsole.StoreActivityIndex(organisationEvent, DbUpdateInfo);
// Construct response.
PopulateEventResponseItemData(model, organisationEvent, responseData.Event);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,727 @@
using Microsoft.AspNetCore.Mvc;
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.Email;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Organisation Controller - Members - Ajax actions.
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Organisation Member data.
private class MemberListData : AjaxResponse_Data.ItemData_Base
{
public String UserAccountId { get; set; }
public bool? CurrentUser { get; set; } = null;
public String EmailAddress { get; set; }
public String DisplayName { get; set; }
public String MemberRoles { get; set; }
public List<String> RoleCodes { get; set; } = new List<string>();
public List<CodeDesc> Roles { get; set; } = new List<CodeDesc>();
public String AdminComment { get; set; }
}
private class MemberItemData : MemberListData
{
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
private void PopulateMemberResponseListData(Model_Organisation_Members model, SaxUserOrganisation userOrganisation, SaxUserAccount userAccount, SaxOrganisation organisation, MemberListData data)
{
data.Id = model.EncryptId(userOrganisation.Id);
data.UserAccountId = model.EncryptId(userOrganisation.UserAccountId);
if (userOrganisation.UserAccountId.Equals(SessionUserAccountId)) data.CurrentUser = true;
data.EmailAddress = userAccount.EmailAddress;
data.DisplayName = userAccount.GetDisplayName();
data.RoleCodes.Clear();
data.Roles.Clear();
foreach (String memberRoleCode in userOrganisation.GetOrganisationRoleCodes())
{
if (StringUtil.IsEmpty(memberRoleCode.Trim())) continue;
Sax.OrganisationRole organisationRole = (Sax.OrganisationRole)OrganisationRoleCodes.GetTypeForCode(memberRoleCode);
if (organisationRole <= 0 || organisationRole == Sax.OrganisationRole.Member) continue;
data.RoleCodes.Add(memberRoleCode);
data.Roles.Add(memberRoleCode, Localiser.GetOrganisationRoleCaption(organisationRole));
}
data.MemberRoles = data.Roles.Select(item => item.Desc).ToList().Concat(", ");
data.AdminComment = userOrganisation.AdminComment;
// NB UserAccount status (not UserOrganisation).
data.StatusCode = userAccount.StatusCode;
data.StatusDesc = userAccount.StatusDesc;
data.CreatedOnFmt = Localiser.FormatDate(userOrganisation.CreateStamp.On);
}
private void PopulateMemberResponseItemData(Model_Organisation_Members model, SaxUserOrganisation userOrganisation, SaxUserAccount userAccount, SaxOrganisation organisation, MemberItemData data)
{
PopulateMemberResponseListData(model, userOrganisation, userAccount, organisation, data as MemberListData);
data.Title = userAccount.Title;
data.FirstName = userAccount.FirstName;
data.LastName = userAccount.LastName;
}
//-------------------------------------------------------------------
// Get Member List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_MemberList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_MemberList(SaxModel_Online model) : base(model) { }
// Member list.
public List<MemberListData> Members { get; } = new List<MemberListData>();
// User profile.
public class UserData
{
public bool CanManage { get; set; }
}
public UserData User { get; } = new UserData();
// Reference data.
public List<CodeDesc> Titles { get; } = new List<CodeDesc>();
public List<CodeDesc> MemberRoles { get; } = new List<CodeDesc>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetMemberList")]
[Route("Organisation/Ajax/GetMembers")]
public IActionResult Ajax_GetMemberList(Model_Organisation_Members model)
{
String fn = GetType().Name + ".Ajax_GetMemberList";
String messageTitle = Localiser["organisation.members"];
AjaxResponse_MemberList responseData = new AjaxResponse_MemberList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Organisation.
SysId organisationId = model.ExtractSysId(model.Filter.OrganisationId);
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Member list. OrganisationId: '" + model.Filter.OrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Store filter data.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
// Reference data - titles.
foreach (TypeCodeDesc title in SaxSystem.GetCodesOfType(Sax.CodeType.Title))
// ToDo Title translations.
responseData.Titles.Add(title.Code, title.Desc);
// Reference data - member roles.
foreach (TypeCodeDesc memberRole in OrganisationRoleCodes.FindAll(item => item.Type != (int)Sax.OrganisationRole.Member))
responseData.MemberRoles.Add(memberRole.Code, Localiser.GetOrganisationRoleCaption((Sax.OrganisationRole)memberRole.Type));
// Session User roles.
responseData.User.CanManage = AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisation.Id, new List<Sax.OrganisationRole> { Sax.OrganisationRole.Admin, Sax.OrganisationRole.MemberAdmin });
// Item filter.
SaxUserOrganisation.Params prmMember = new SaxUserOrganisation.Params();
prmMember.OrganisationIdsIncluded.Add(organisationId);
prmMember.UserAccountStatusCat = responseData.User.CanManage ? SaxUserAccount.StatusCat.AwaitingOrActive : SaxUserAccount.StatusCat.ActiveOnly;
prmMember.OrganisationMembersOnly = true;
prmMember.UserFilterPartial = model.Filter.Member;
prmMember.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserOrganisationCount(prmMember);
model.PopulatePagingTo(prmMember.ResultsPaging);
// Item columns.
ColumnDfn colId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Id);
ColumnDfn colUserAccountId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.UserAccountId);
ColumnDfn colEmailAddress = responseData.User.CanManage ? AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.User_EmailAddress) : new ColumnDfn(-1, "");
ColumnDfn colDisplayName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.User_DisplayName);
ColumnDfn colOrganisationRoleCodes = responseData.User.CanManage ? AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationRoleCodes) : new ColumnDfn(-1, "");
ColumnDfn colUserStatus = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.User_Status);
ColumnDfn colAdminComment = responseData.User.CanManage ? AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.AdminComment) : new ColumnDfn(-1, "");
ColumnDfn colCreatedOn = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.CreateOn_Date);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colUserAccountId , colEmailAddress, colDisplayName,
colOrganisationRoleCodes, colUserStatus,
colAdminComment, colCreatedOn
};
// Sort columns.
colCreatedOn.SortAscending = false;
prmMember.SortColumns.Add(colDisplayName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(columns, prmMember))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
MemberListData memberData = new MemberListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
UserAccountId = model.EncryptId(new SysId(values.GetLong(colUserAccountId.DataName))),
EmailAddress = values.GetString(colEmailAddress.DataName),
DisplayName = AccountMgr.GetUserOrganisationDataValue(colDisplayName, values).ToString(),
CreatedOnFmt = Localiser.FormatDate(values.GetDateTime(colCreatedOn.DataName)),
AdminComment = values.GetString(colAdminComment.DataName),
// NB UserAccount status (not UserOrganisation).
StatusCode = values.GetString(colUserStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colUserStatus.DataName))
};
if (model.ExtractSysId(memberData.UserAccountId).Equals(SessionUserAccountId)) memberData.CurrentUser = true;
String memberRoleCodes = values.GetString(colOrganisationRoleCodes.DataName);
foreach (String memberRoleCode in StringListUtil.Split(memberRoleCodes, SaxUserOrganisation.UserOrgnisationRoleDelim))
{
if (StringUtil.IsEmpty(memberRoleCode.Trim())) continue;
Sax.OrganisationRole organisationRole = (Sax.OrganisationRole)OrganisationRoleCodes.GetTypeForCode(memberRoleCode);
if (organisationRole <= 0 || organisationRole == Sax.OrganisationRole.Member) continue;
memberData.RoleCodes.Add(memberRoleCode);
memberData.Roles.Add(memberRoleCode, Localiser.GetOrganisationRoleCaption(organisationRole));
}
memberData.MemberRoles = memberData.Roles.Select(item => item.Desc).ToList().Concat(", ");
responseData.Members.Add(memberData);
}
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Members.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Member ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Member : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Member(SaxModel_Online model) : base(model) { }
// Member details.
public MemberItemData Member { get; } = new MemberItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetMember")]
public IActionResult Ajax_GetMember(Model_Organisation_Members model)
{
String fn = GetType().Name + ".Ajax_GetMember";
String messageTitle = Localiser["label.member"];
AjaxResponse_Member responseData = new AjaxResponse_Member(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Identify item.
SysId memberId = model.ExtractSysId(model.MemberId);
if (!SysId.IsValid(memberId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve item.
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationById(memberId);
disposableItems.Add(userOrganisation);
if (userOrganisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve Organisation Member for Id: '" + model.MemberId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(userOrganisation.OrganisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " Organisation for Member. Id: '" + userOrganisation.OrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Session User roles.
bool userCanManageMembers = AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisation.Id, new List<Sax.OrganisationRole> { Sax.OrganisationRole.Admin, Sax.OrganisationRole.MemberAdmin });
// Validate user account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userOrganisation.UserAccountId);
disposableItems.Add(userAccount);
bool userActive = userAccount.IsStatusActive || (userAccount.IsStatusAwaiting && userCanManageMembers);
if (userAccount == null || !userActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " User Account for Member. Id: '" + userOrganisation.UserAccountId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Make a note of the selected Organisation.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
PopulateMemberResponseItemData(model, userOrganisation, userAccount, organisation, responseData.Member);
}
// Construct response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Member ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_StoreMember : AjaxResponse_Data
{
// Construction.
public AjaxResponse_StoreMember(SaxModel_Online model) : base(model) { }
// Member details.
public MemberItemData Member { get; } = new MemberItemData();
// Feedback.
public bool? MemberNameRequired { get; set; } = null;
}
[HttpPost]
[Route("Organisation/Ajax/StoreMember")]
public IActionResult Ajax_StoreMember(Model_Organisation_Members model)
{
String fn = GetType().Name + ".Ajax_StoreMember";
String messageTitle = Localiser["label.member"];
AjaxResponse_StoreMember responseData = new AjaxResponse_StoreMember(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Validate item properties.
model.ValidateMemberData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
//---------------------------------------------------------------
// User Organisation identifiers.
// New Member:
// Organisation Id [MemberOrganisationId].
// Email address [MemberEmailAddress].
// Existing member:
// User Organisation Id [MemberId].
//---------------------------------------------------------------
// Validate Member against Id against 'Exists' flag.
SysId memberId = model.ExtractSysId(model.MemberId);
bool memberExists = StringUtil.ToBool(model.MemberExists);
if (memberExists && !SysId.IsValid(memberId))
{
SaLogger.Warn(fn + " Id not specified for existing User Organisation.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!memberExists && SysId.IsValid(memberId))
{
SaLogger.Warn(fn + " Id specified for new User Organisation: '" + model.MemberId + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve existing item.
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationById(memberId);
disposableItems.Add(userOrganisation);
if (userOrganisation == null && SysId.IsValid(memberId))
{
SaLogger.Warn(fn + " Failed to retrieve User Organisation for Id: '" + model.MemberId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate the Organisation.
SysId organisationId = userOrganisation != null ? userOrganisation.OrganisationId : model.ExtractSysId(model.MemberOrganisationId);
if (!SysId.IsValid(organisationId))
{
SaLogger.Warn(fn + " Organisation Id not specified for Member.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " Organisation for User Organisation. OrganisationId: '" + model.MemberOrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (userOrganisation != null && !userOrganisation.OrganisationId.Equals(organisationId))
{
SaLogger.Warn(fn + " Inconsistent Organisation Id specified for User Organisation. Expected: " + userOrganisation.OrganisationId + ". Found: " + organisationId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Ensure that the user has the correct role to update Members for this organisation.
bool userCanManageMembers = AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisationId, new List<Sax.OrganisationRole> { Sax.OrganisationRole.Admin, Sax.OrganisationRole.MemberAdmin });
if (!userCanManageMembers)
{
SaLogger.Warn(fn + " User doesn't have 'Member Admin' role for Organisation Id: " + organisationId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Validate the User Account for an existing member.
SaxUserAccount userAccount = null;
if (userOrganisation != null)
{
userAccount = AccountMgr.GetUserAccountById(userOrganisation.UserAccountId);
disposableItems.Add(userAccount);
bool userActive = userAccount.IsStatusActive || (userAccount.IsStatusAwaiting && userCanManageMembers);
if (userAccount == null || !userActive)
{
SaLogger.Warn(fn + " " + (userAccount == null ? "Invalid" : "Inactive") + " User Account for User Organisation. UserAccountId: '" + userOrganisation.UserAccountId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (userOrganisation != null && !userOrganisation.UserAccountId.Equals(userAccount.Id))
{
SaLogger.Warn(fn + " Inconsistent User Account Id specified for User Organisation. Expected: " + userOrganisation.UserAccountId + ". Found: " + userAccount.Id);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Validate roles for current user.
if (userOrganisation.UserAccountId.Equals(SessionUserAccountId))
{
// User is modifying their own organisation member.
if (userOrganisation.CanManageOrganisation && !model.OrganisationRoleCodes.Any(item => item.Type == (int)Sax.OrganisationRole.Admin))
{
// User has removed their own 'Admin' role.
SaLogger.Warn(fn + " Unable to store member - user is attempting to remove their own 'Admin' role.");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["organisation.members.msg.remove.own.admin.role"]));
}
if (!userOrganisation.CanManageMembers)
{
SaLogger.Warn(fn + " Unable to store member - user is attempting to remove their own 'Admin' and/or 'Member Admin' role.");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["organisation.members.msg.remove.own.admin.role"]));
}
}
}
// New member - Lookup user by email and create new account if needed.
if (userAccount == null)
{
// Check if account exists for email.
userAccount = AccountMgr.GetUserAccountByEmailAddress(model.MemberEmailAddress);
if (userAccount != null)
{
// Existing User Account - get User Organisation if available.
userOrganisation = AccountMgr.GetUserOrganisationByUserOrganisation(userAccount.Id, organisation.Id);
disposableItems.Add(userOrganisation);
}
else
{
// If name hasn't been specified, return to page to complete details.
if (!StringUtil.ToBool(model.MemberNameSupplied))
{
responseData.SetControlError(nameof(model.MemberFirstName), Localiser["msg.validation.mandatory.value"]);
responseData.SetControlError(nameof(model.MemberLastName), Localiser["msg.validation.mandatory.value"]);
responseData.MemberNameRequired = true;
responseData.Success = true;
return Json(responseData);
}
// Create new User Account.
userAccount = AccountMgr.CreateUserAccount();
userAccount.EmailAddress = model.MemberEmailAddress;
userAccount.Status = SaStatus.Awaiting;
}
disposableItems.Add(userAccount);
}
// Store user account.
if (StringUtil.ToBool(model.MemberNameSupplied))
{
if (model.MemberTitle != null)
userAccount.Title = model.MemberTitle;
if (model.MemberFirstName != null)
userAccount.FirstName = model.MemberFirstName;
if (model.MemberLastName != null)
userAccount.LastName = model.MemberLastName;
}
if (!SysId.IsValid(userAccount.Id))
{
if (!AccountMgr.InsertUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert User Account: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert User Account: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Create new item if required.
if (userOrganisation == null)
{
userOrganisation = AccountMgr.CreateUserOrganisation(userAccount.Id, organisation.Id);
disposableItems.Add(userOrganisation);
}
// User Organisation Roles.
if (model.MemberRoleCodes != null)
userOrganisation.OrganisationRoles = SaxUserOrganisation.GetOrganisationRolesFromCodes(model.GetMemberRoleCodes());
// Ensure user has member role.
userOrganisation.AddOrganisationRole(Sax.OrganisationRole.Member);
// User organisation properties.
if (model.MemberAdminComment != null)
userOrganisation.AdminComment = model.MemberAdminComment;
userOrganisation.Status = SaStatus.Active;
// Store item.
if (!SysId.IsValid(userOrganisation.Id))
{
if (!AccountMgr.InsertUserOrganisation(userOrganisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert User Organisation: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!AccountMgr.UpdateUserOrganisation(userOrganisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update User Organisation [Id:" + SysId.ToString(userOrganisation.Id) + "]: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Construct response.
PopulateMemberResponseItemData(model, userOrganisation, userAccount, organisation, responseData.Member);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Activate Member ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_ActivateMember : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ActivateMember(SaxModel_Online model) : base(model) { }
// Member details.
public MemberItemData Member { get; } = new MemberItemData();
}
[HttpPost]
[Route("Organisation/Ajax/ActivateMember")]
public IActionResult Ajax_ActivateMember(Model_Organisation_Members model)
{
String fn = GetType().Name + ".Ajax_ActivateMember";
String messageTitle = Localiser["label.member"];
AjaxResponse_ActivateMember responseData = new AjaxResponse_ActivateMember(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve User Organisation [MemberId).
SysId userOrganisationId = model.ExtractSysId(model.MemberId);
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationById(userOrganisationId);
disposableItems.Add(userOrganisation);
if (userOrganisation == null || !userOrganisation.IsStatusActive)
{
SaLogger.Warn(fn + " User Organisation not found or not active. Id: " + SysId.ToString(userOrganisationId));
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve the Organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(userOrganisation.OrganisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " Organisation not found or not active. Id: " + SysId.ToString(userOrganisation.OrganisationId));
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Ensure that the user has the correct role to update Members for this organisation.
bool userCanManageMembers = AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisation.Id, new List<Sax.OrganisationRole> { Sax.OrganisationRole.Admin, Sax.OrganisationRole.MemberAdmin });
if (!userCanManageMembers)
{
SaLogger.Warn(fn + " User doesn't have 'Member Admin' role for Organisation Id: " + organisation.Id);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve User Account.
SaxUserAccount userAccount = null;
userAccount = AccountMgr.GetUserAccountById(userOrganisation.UserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Account. Id: " + userOrganisation.UserAccountId);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!userAccount.IsStatusAwaiting)
{
SaLogger.Warn(fn + " User Account is not awaiting. Id: " + userOrganisation.UserAccountId + ". Status: " + userAccount.StatusDesc);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Update user's status.
userAccount.Status = SaStatus.Active;
if (AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
// Send activation email.
SaEmailData emailData = new SaEmailData();
emailData.Config = EmailConfig;
emailData.AddressesTo.Add(userAccount.EmailAddress);
emailData.Subject = Localiser["account.activate.email.subject"];
PopulateAccountActivatedEmailBody(emailData, userAccount);
if (EmailConsole.SendEmail(emailData))
SaLogger.Info(fn + " User Activation email sent to " + userAccount.EmailAddress, true);
else SaLogger.Warn(fn + " Failed to send User Activation email to " + userAccount.EmailAddress);
// Treat the activation as successful regardless of whether the email was sent.
responseData.SetNotifyMessage(messageTitle, Localiser["account.activate.notify"]);
}
else
{
SaLogger.Warn(fn + " Failed to insert User Account: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
// Construct response.
PopulateMemberResponseItemData(model, userOrganisation, userAccount, organisation, responseData.Member);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
private void PopulateAccountActivatedEmailBody(SaEmailData emailData, SaxUserAccount userAccount)
{
// Will need to handle culture (language/country) as and when introduced.
emailData.MessageBody = SaxResourceHelper.GetResourceText(SaxResourceHelper.TextResource.Email_Security_AccountActivated_Html);
emailData.HtmlBody = true;
// Replace dynamic tags.
EmailConsole.ReplaceDynamicEmailBodyTags(emailData, null, userAccount);
// Replace dynamic tag for link back to change password page.
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.RequestPassword]", SecurityController.GenerateRequestPasswordLink(this, userAccount.Id, userAccount.EmailAddress, ""));
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.SignIn]", GetRedirectLocation(RedirectLocation.WebAppBase) + "Security/SignIn");
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.ExpiresMinutes]", SecurityController.GetRequestPasswordEmailLinkExpiresMinutes(this).ToString());
}
//-------------------------------------------------------------------
// Remove Member ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_RemoveMember : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RemoveMember(SaxModel_Online model) : base(model) { }
// Member details.
public MemberItemData Member { get; } = new MemberItemData();
}
[HttpPost]
[Route("Organisation/Ajax/RemoveMember")]
public IActionResult Ajax_RemoveMember(Model_Organisation_Members model)
{
String fn = GetType().Name + ".Ajax_RemoveMember";
String messageTitle = Localiser["label.member"];
AjaxResponse_RemoveMember responseData = new AjaxResponse_RemoveMember(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve User Organisation [MemberId).
SysId userOrganisationId = model.ExtractSysId(model.MemberId);
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisationById(userOrganisationId);
disposableItems.Add(userOrganisation);
if (userOrganisation == null || !userOrganisation.IsStatusActive)
{
SaLogger.Warn(fn + " User Organisation not found or not active. Id: " + SysId.ToString(userOrganisationId));
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve the Organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(userOrganisation.OrganisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " Organisation not found or not active. Id: " + SysId.ToString(userOrganisation.OrganisationId));
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Ensure that the user has the correct role to update Members for this organisation.
bool userCanManageMembers = AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisation.Id, new List<Sax.OrganisationRole> { Sax.OrganisationRole.Admin, Sax.OrganisationRole.MemberAdmin });
if (!userCanManageMembers)
{
SaLogger.Warn(fn + " User doesn't have 'Member Admin' role for Organisation Id: " + organisation.Id);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve User Account.
SaxUserAccount userAccount = null;
userAccount = AccountMgr.GetUserAccountById(userOrganisation.UserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Account. Id: " + userOrganisation.UserAccountId);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (!userAccount.IsStatusAwaiting && !userAccount.IsStatusActive)
{
SaLogger.Warn(fn + " User Account is not awaiting or active. Id: " + userOrganisation.UserAccountId);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Remove User Organisation roles.
userOrganisation.ClearOrganisationRoles();
if (AccountMgr.UpdateUserOrganisation(userOrganisation, DbUpdateInfo))
{
responseData.SetNotifyMessage(messageTitle, Localiser["msg.item.removed"]);
}
else
{
SaLogger.Warn(fn + " Failed to update User Organisation: " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
// Construct response.
PopulateMemberResponseItemData(model, userOrganisation, userAccount, organisation, responseData.Member);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,423 @@
using Microsoft.AspNetCore.Mvc;
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.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Organisation Controller - Opportunities - Ajax actions.
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Organisation Opportunity data.
private class OpportunityListData : AjaxResponse_Data.ItemData_Base
{
public String TypeCode { get; set; }
public String TypeDesc { get; set; }
public String Position { get; set; }
public String Outline { get; set; }
public String RegionCaption { get; set; }
public String SportCaption { get; set; }
public String PublishedOnFmt { get; set; }
public String ExpiresOnFmt { get; set; }
}
private class OpportunityItemData : OpportunityListData
{
public String RegionIdPrimary { get; set; }
public String SportIdPrimary { get; set; }
public String PublishedOn_YYYYMMDD { get; set; }
public String ExpiresOn_YYYYMMDD { get; set; }
}
private void PopulateOpportunityResponseListData(Model_Organisation_Opportunities model, SaxOrganisationOpportunity opportunity, OpportunityListData data)
{
data.Id = model.EncryptId(opportunity.Id);
data.TypeCode = OpportunityTypeCodes.GetCodeForType(opportunity.OpportunityType);
data.TypeDesc = Localiser.GetOpportunityTypeCaption(opportunity.OpportunityType);
data.Position = opportunity.Position;
data.Outline = opportunity.Outline;
data.RegionCaption = model.GetRegionCaption(opportunity.RegionIdPrimary);
data.SportCaption = model.GetSportCaption(opportunity.SportIdPrimary);
data.CreatedOnFmt = Localiser.FormatDate(opportunity.CreateStamp.On);
data.PublishedOnFmt = Localiser.FormatDate(opportunity.PublishedAt);
data.ExpiresOnFmt = Localiser.FormatDate(opportunity.ExpiresAt);
data.StatusCode = opportunity.StatusCode;
data.StatusDesc = opportunity.StatusDesc;
}
private void PopulateOpportunityResponseItemData(Model_Organisation_Opportunities model, SaxOrganisationOpportunity opportunity, OpportunityItemData data)
{
PopulateOpportunityResponseListData(model, opportunity, data as OpportunityListData);
data.RegionIdPrimary = model.GetEncryptedRegionId(opportunity.RegionIdPrimary);
data.SportIdPrimary = model.GetEncryptedSportId(opportunity.SportIdPrimary);
data.PublishedOn_YYYYMMDD = DateUtil.Format(opportunity.PublishedAt, "yyyyMMdd");
data.ExpiresOn_YYYYMMDD = DateUtil.Format(opportunity.ExpiresAt, "yyyyMMdd");
}
//-------------------------------------------------------------------
// Get Opportunity List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_OpportunityList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_OpportunityList(SaxModel_Online model) : base(model) { }
// Opportunity list.
public List<OpportunityListData> Opportunities { get; } = new List<OpportunityListData>();
// User profile.
public class UserData
{
public bool CanManage { get; set; }
}
public UserData User { get; } = new UserData();
// Organisation.
public class OrganisationData
{
public String Id { get; set; }
public String Name { get; set; }
public String SportId { get; set; }
public String SportName { get; set; }
public String RegionId { get; set; }
public String RegionName { get; set; }
}
public OrganisationData Organisation { get; } = new OrganisationData();
// Reference data.
public List<CodeDesc> OpportunityTypes { get; } = new List<CodeDesc>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetOpportunityList")]
[Route("Organisation/Ajax/GetOpportunities")]
public IActionResult Ajax_GetOpportunityList(Model_Organisation_Opportunities model)
{
String fn = GetType().Name + ".Ajax_GetOpportunityList";
String messageTitle = Localiser["organisation.caption.opportunities"];
AjaxResponse_OpportunityList responseData = new AjaxResponse_OpportunityList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
SysId userAccountId = SessionUserAccountId;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Organisation.
SysId organisationId = model.ExtractSysId(model.Filter.OrganisationId);
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Opportunity list. OrganisationId: '" + model.Filter.OrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (organisation != null)
{
// Store filter data.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
// Reference data - opportunity types.
foreach (TypeCodeDesc opportunityType in OpportunityTypeCodes)
responseData.OpportunityTypes.Add(opportunityType.Code, Localiser.GetOpportunityTypeCaption((Sax.OpportunityType)opportunityType.Type));
// User organisation.
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(userAccountId);
prmUserOrganisation.OrganisationIdsIncluded.Add(organisationId);
prmUserOrganisation.UserAccountStatusCat = SaxUserAccount.StatusCat.ActiveOnly;
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
SaxUserOrganisation userOrganisation = AccountMgr.GetUserOrganisation(prmUserOrganisation);
disposableItems.Add(userOrganisation);
if (userOrganisation == null)
{
SaLogger.Warn(fn + " Invalid/inactive user organisation for Opportunity list. UserAccountId: " + SysId.ToString(userAccountId) + ". OrganisationId: " + model.Filter.OrganisationId + ".");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Organisation.
responseData.Organisation.Id = SysId.ToString(organisation.Id);
responseData.Organisation.Name = organisation.Name;
responseData.Organisation.SportId = model.GetEncryptedSportId(organisation.SportIdPrimary);
responseData.Organisation.SportName = model.GetSportCaption(organisation.SportIdPrimary);
responseData.Organisation.RegionId = model.GetEncryptedRegionId(organisation.RegionIdPrimary);
responseData.Organisation.RegionName = model.GetRegionCaption(organisation.RegionIdPrimary);
// User details.
responseData.User.CanManage = userOrganisation.CanManageOpportunities;
// Item filter.
SaxOrganisationOpportunity.Params prmOpportunity = new SaxOrganisationOpportunity.Params();
prmOpportunity.UserAccountIdsIncluded.Add(userAccountId);
prmOpportunity.OrganisationIdsIncluded.Add(organisationId);
prmOpportunity.OpportunityFilter = model.Filter.Opportunity;
prmOpportunity.StatusCat = SaxOrganisationOpportunity.StatusCat.NotDeleted;
model.Paging.ItemTotal = (ulong)ActivityMgr.GetOrganisationOpportunityCount(prmOpportunity);
model.PopulatePagingTo(prmOpportunity.ResultsPaging);
// Item columns.
ColumnDfn colId = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Id);
ColumnDfn colOpportunityTypeCode = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.OpportunityTypeCode);
ColumnDfn colPosition = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Position);
ColumnDfn colOutline = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Outline);
ColumnDfn colRegionId = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.RegionIdPrimary);
ColumnDfn colRegionName = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Region_Name);
ColumnDfn colSportId = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.SportIdPrimary);
ColumnDfn colSportName = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Sport_Name);
ColumnDfn colPublishedOn = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.PublishedAt_Date);
ColumnDfn colExpiresOn = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.ExpiresAt_Date);
ColumnDfn colStatus = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.Status);
ColumnDfn colCreatedOn = ActivityMgr.GetOrganisationOpportunityColumnDfn(SaxOrganisationOpportunity.Column.CreateOn_Date);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colOpportunityTypeCode, colPosition, colOutline,
colRegionId, colRegionName, colSportId, colSportName,
colPublishedOn, colExpiresOn,
colStatus, colCreatedOn
};
// Sort columns.
colCreatedOn.SortAscending = false;
prmOpportunity.SortColumns.Add(colCreatedOn);
// Query items.
using (DbResultSet resultSet = ActivityMgr.ExecuteOrganisationOpportunityQuery(columns, prmOpportunity))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
OpportunityListData opportunityData = new OpportunityListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
TypeCode = values.GetString(colOpportunityTypeCode.DataName),
TypeDesc = Localiser.GetOpportunityTypeCaption((Sax.OpportunityType)OpportunityTypeCodes.GetTypeForCode(values.GetString(colOpportunityTypeCode.DataName))),
Position = values.GetString(colPosition.DataName),
Outline = values.GetString(colOutline.DataName),
RegionCaption = model.GetRegionCaption(SysId.ToSysId(values.GetString(colRegionId.DataName))),
SportCaption = model.GetSportCaption(SysId.ToSysId(values.GetString(colSportId.DataName))),
CreatedOnFmt = Localiser.FormatDate(values.GetDateTime(colCreatedOn.DataName)),
PublishedOnFmt = Localiser.FormatDate(values.GetDateTime(colPublishedOn.DataName)),
ExpiresOnFmt = Localiser.FormatDate(values.GetDateTime(colExpiresOn.DataName)),
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Opportunities.Add(opportunityData);
}
}
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Opportunities.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Opportunity ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_Opportunity : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Opportunity(SaxModel_Online model) : base(model) { }
// Opportunity details.
public OpportunityItemData Opportunity { get; } = new OpportunityItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetOpportunity")]
public IActionResult Ajax_GetOpportunity(Model_Organisation_Opportunities model)
{
String fn = GetType().Name + ".Ajax_GetOpportunity";
String messageTitle = Localiser["organisation.caption.opportunity"];
AjaxResponse_Opportunity responseData = new AjaxResponse_Opportunity(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId opportunityId = model.ExtractSysId(model.OpportunityId);
if (!SysId.IsValid(opportunityId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxOrganisationOpportunity opportunity = ActivityMgr.GetOrganisationOpportunityById(opportunityId);
disposableItems.Add(opportunity);
if (opportunity == null)
{
SaLogger.Warn(fn + " Failed to retrieve Organisation Opportunity for Id: '" + model.OpportunityId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate organisation.
SaxOrganisation organisation = AccountMgr.GetOrganisationById(opportunity.OrganisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Opportunity. OrganisationId: '" + opportunity.OrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Make a note of the selected Organisation.
SessionData_Online.Filter.SetMemberOrganisation(organisation);
// Construct response.
PopulateOpportunityResponseItemData(model, opportunity, responseData.Opportunity);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Store Opportunity ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/StoreOpportunity")]
public IActionResult Ajax_StoreOpportunity(Model_Organisation_Opportunities model)
{
String fn = GetType().Name + ".Ajax_StoreOpportunity";
String messageTitle = Localiser["organisation.caption.opportunity"];
AjaxResponse_Opportunity responseData = new AjaxResponse_Opportunity(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateOpportunityData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Validate Opportunity against Id against 'Exists' flag.
SysId opportunityId = model.ExtractSysId(model.OpportunityId);
bool opportunityExists = StringUtil.ToBool(model.OpportunityExists);
if (opportunityExists && !SysId.IsValid(opportunityId))
{
SaLogger.Warn(fn + " Id not specified for existing Opportunity.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!opportunityExists && SysId.IsValid(opportunityId))
{
SaLogger.Warn(fn + " Id specified for new Opportunity: '" + model.OpportunityId + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve existing item.
SaxOrganisationOpportunity opportunity = ActivityMgr.GetOrganisationOpportunityById(opportunityId);
disposableItems.Add(opportunity);
if (opportunity == null && SysId.IsValid(opportunityId))
{
SaLogger.Warn(fn + " Failed to retrieve opportunity for Id: '" + model.OpportunityId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate the Organisation.
SysId organisationId = model.ExtractSysId(model.OpportunityOrganisationId);
if (!SysId.IsValid(organisationId))
{
SaLogger.Warn(fn + " Organisation Id not specified for Opportunity.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
disposableItems.Add(organisation);
if (organisation == null || !organisation.IsStatusActive)
{
SaLogger.Warn(fn + " " + (organisation == null ? "Invalid" : "Inactive") + " organisation for Opportunity. OrganisationId: '" + model.OpportunityOrganisationId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
if (opportunity != null && !opportunity.OrganisationId.Equals(organisationId))
{
SaLogger.Warn(fn + " Inconsistent Organisation Id specified for Opportunity. Expected: " + opportunity.OrganisationId + ". Found: " + organisationId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Ensure that the user has the correct role to update Opportunities for this organisation.
if (!AccountMgr.UserHasAnyOrganisationRole(SessionUserAccountId, organisationId, new[] { Sax.OrganisationRole.Admin, Sax.OrganisationRole.OpportunityAdmin }))
{
SaLogger.Warn(fn + " User doesn't have 'Opportunity Admin' role for Organisation Id: " + organisationId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Create new item if required.
if (opportunity == null)
{
opportunity = ActivityMgr.CreateOrganisationOpportunity(organisationId);
disposableItems.Add(opportunity);
}
// Populate item properties.
if (model.OpportunityRegionId != null)
opportunity.RegionIdPrimary = model.ExtractSysId(model.OpportunityRegionId);
if (model.OpportunitySportId != null)
opportunity.SportIdPrimary = model.ExtractSysId(model.OpportunitySportId);
if (model.OpportunityTypeCode != null)
opportunity.OpportunityType = (Sax.OpportunityType)OpportunityTypeCodes.GetTypeForCode(model.OpportunityTypeCode);
if (model.OpportunityPosition != null)
opportunity.Position = model.OpportunityPosition;
if (model.OpportunityOutline != null)
opportunity.Outline = model.OpportunityOutline;
if (model.OpportunityStatusCode != null)
opportunity.Status = model.OpportunityStatus;
// Store item.
bool opportunityCreated = false;
if (!SysId.IsValid(opportunity.Id))
{
if (!ActivityMgr.InsertOrganisationOpportunity(opportunity, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert opportunity: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
opportunityCreated = true;
}
else
{
if (!ActivityMgr.UpdateOrganisationOpportunity(opportunity, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update opportunity [Id:" + SysId.ToString(opportunity.Id) + "]: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
SaLogger.Info(fn + " Opportunity " + (opportunityCreated ? "created" : "updated") + ". Id: " + opportunity.Id.ToString() + ". User: " + SessionData.User.EmailAddress + ". Organisation: " + organisation.Name, true);
// Store Opportunity Activity Index data.
ActivityConsole.StoreActivityIndex(opportunity, DbUpdateInfo);
// Construct response.
PopulateOpportunityResponseItemData(model, opportunity, responseData.Opportunity);
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,214 @@
using Microsoft.AspNetCore.Mvc;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Organisation Controller - Request - Ajax actions.
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Request Organisation ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_RequestOrganisation : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RequestOrganisation(SaxModel_Online model) : base(model) { }
// Organisation list.
public List<OrganisationListData> Organisations { get; } = new List<OrganisationListData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/Request")]
[Route("Organisation/Ajax/Register")]
public IActionResult Ajax_RequestOrganisation(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_RequestOrganisation";
String messageTitle = Localiser["organisation.caption.organisation.request"];
AjaxResponse_RequestOrganisation responseData = new AjaxResponse_RequestOrganisation(model);
// Validate session and referrer. User does not need to be logged in.
if (!ValidReferer) return Forbid();
SaxSessionData_Online sessionData = SessionData_Online;
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
if (!model.ValidateOrganisationRequestData(responseData))
return Json(responseData);
// Check Regions.
SysId regionIdCountry = model.ExtractSysId(model.Organisation.RegionIdPrimary);
SaxRegion.Data regionCountry = Internals.GetRegion(regionIdCountry);
if (SysId.IsValid(regionIdCountry) && regionCountry == null)
{
SaLogger.Warn(fn + " Country region not found. Id: " + model.Organisation.RegionIdPrimary + ". " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
SysId regionIdAddressProvince = model.ExtractSysId(model.Organisation.Address.RegionIdProvince);
SaxRegion.Data regionProvince = Internals.GetRegion(regionIdAddressProvince);
if (SysId.IsValid(regionIdAddressProvince) && regionProvince == null)
{
SaLogger.Warn(fn + " Province region not found. Id: " + model.Organisation.Address.RegionIdProvince + ". " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// User does not need to be logged in, but user details must be supplied if not.
SaxUserAccount userAccount = null;
if (ValidUserSession)
{
// Check that an email address hasn't been supplied.
// NB Shouldn't happen as contact controls aren't displayed if user is logged in,
// hence 'System' error response and nothing more specific.
if (!StringUtil.IsEmpty(model.ContactEmailAddress))
{
SaLogger.Warn(fn + " Contact Email supplied when user logged in. " + GetLogSuffix());
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve user account.
userAccount = AccountMgr.GetUserAccountById(sessionData.User.UserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
else
{
// Double check that contact details have been supplied - validated by model above.
if (StringUtil.IsEmpty(model.ContactEmailAddress) || StringUtil.IsEmpty(model.ContactFirstName) || StringUtil.IsEmpty(model.ContactLastName))
{
SaLogger.Warn(fn + " Incomplete contact details supplied.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Check that the email doesn't exist.
// NB This is validated by the model and shouldn't be required here.
userAccount = AccountMgr.GetUserAccountByEmailAddress(model.ContactEmailAddress);
disposableItems.Add(userAccount);
if (userAccount != null)
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
userAccount = AccountMgr.CreateUserAccount();
userAccount.EmailAddress = model.ContactEmailAddress;
userAccount.FirstName = model.ContactFirstName;
userAccount.LastName = model.ContactLastName;
if (!AccountMgr.InsertUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user contact - " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
SysId organisationSportId = model.ExtractSysId(model.Organisation.SportId);
if (SysId.IsValid(organisationSportId))
{
// Add User Sport if not already present.
// NB This will also reactivate an inactive or deleted User Sport.
SaxUserSport.Params prmUserSport = new SaxUserSport.Params();
prmUserSport.UserAccountIdsIncluded.Add(userAccount.Id);
prmUserSport.SportIdsIncluded.Add(organisationSportId);
prmUserSport.StatusCat = SaxUserSport.StatusCat.Any;
SaxUserSport userSport = AccountMgr.GetUserSport(prmUserSport);
if (userSport == null)
userSport = AccountMgr.CreateUserSport(userAccount.Id, organisationSportId);
disposableItems.Add(userSport);
userSport.Status = SaxUserSport.DefaultStatus;
if (!SysId.IsValid(userSport.Id))
{
if (!AccountMgr.InsertUserSport(userSport, DbUpdateInfo))
{
// Logg error but don't abort the request.
SaLogger.Warn(fn + " Failed to insert User Sport: " + AccountMgr.ErrorStateMessage);
}
}
else
{
if (!AccountMgr.UpdateUserSport(userSport, DbUpdateInfo))
{
// Logg error but don't abort the request.
SaLogger.Warn(fn + " Failed to update User Sport: " + AccountMgr.ErrorStateMessage);
}
}
}
// Create Organisation.
SaxOrganisation organisation = AccountMgr.CreateOrganisation();
disposableItems.Add(organisation);
organisation.Name = model.Organisation.Name;
organisation.OrganisationType = model.Organisation.GetOrganisationType();
organisation.OrganisationCategory = model.Organisation.GetOrganisationCategory();
organisation.SportIdPrimary = organisationSportId;
organisation.RegionIdPrimary = regionIdCountry;
organisation.Status = SaStatus.Awaiting;
if (!AccountMgr.InsertOrganisation(organisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert organisation - " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Add user as member of organisation.
SaxUserOrganisation userOrganisation = AccountMgr.CreateUserOrganisation(userAccount.Id, organisation.Id);
disposableItems.Add(userOrganisation);
userOrganisation.OrganisationRoles = new[] { Sax.OrganisationRole.Member, Sax.OrganisationRole.Admin };
if (!AccountMgr.InsertUserOrganisation(userOrganisation, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user organisation - " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Create Organisation address.
SaxItemAddress organisationAddress = ItemDataMgr.CreateItemAddress(organisation.GetItemId(), SaxItemAddress.DefaultOrganisationAddressType);
disposableItems.Add(organisationAddress);
organisationAddress.Line1 = model.Organisation.Address.Line1;
organisationAddress.Line2 = model.Organisation.Address.Line2;
organisationAddress.Line3 = model.Organisation.Address.Line3;
organisationAddress.Town = model.Organisation.Address.Town;
if (regionProvince != null)
{
organisationAddress.Province = regionProvince.Name;
organisationAddress.RegionIdProvince = regionProvince.Id;
}
else
{
if (model.Organisation.Address.Province != null)
organisationAddress.Province = model.Organisation.Address.Province;
}
if (regionCountry != null)
{
organisationAddress.Country = regionCountry.Name;
organisationAddress.RegionIdCountry = regionCountry.Id;
}
else
{
if (model.Organisation.Address.Country != null)
organisationAddress.Country = model.Organisation.Address.Country;
}
organisationAddress.PostalCode = model.Organisation.Address.PostalCode;
if (!ItemDataMgr.InsertItemAddress(organisationAddress, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert organisation address - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// Complete response.
responseData.SetPopupMessage(messageTitle, Localiser["msg.organisation.request.success"].ToString().ReplaceAll("\n", "<br/><br/>"));
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,344 @@
using Microsoft.AspNetCore.Mvc;
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.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Organisation Controller - Ajax actions.
//=======================================================================
partial class OrganisationController
{
//-------------------------------------------------------------------
// Organisation data.
private class OrganisationListData : AjaxResponse_Data.ItemData_Base
{
public String Name { get; set; }
public String RegionId { get; set; }
public String RegionName { get; set; }
public String SportId { get; set; }
public String SportName { get; set; }
public String LogoUrl { get; set; }
}
//-------------------------------------------------------------------
// Get Organisation List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_OrganisationList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_OrganisationList(SaxModel_Online model) : base(model) { }
// Organisation list.
public List<OrganisationListData> Organisations { get; } = new List<OrganisationListData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetOrganisationList")]
public IActionResult Ajax_GetOrganisationList(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_GetOrganisationList";
String messageTitle = Localiser["organisation.caption.organisations"];
AjaxResponse_OrganisationList responseData = new AjaxResponse_OrganisationList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Query columns.
ColumnDfn colOrganisationId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Id);
ColumnDfn colOrganisationName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name);
ColumnDfn colRegionId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.RegionId);
ColumnDfn colRegionName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Region_Name);
ColumnDfn colSportId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.SportId);
ColumnDfn colSportName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Sport_Name);
ColumnDfn colLogoCount = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.LogoImage_Count);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colOrganisationId, colOrganisationName,
colRegionId, colRegionName,
colSportId, colSportName,
colLogoCount
};
// Organisation filter.
SaxOrganisation.Params prmOrganisation = new SaxOrganisation.Params();
prmOrganisation.OrganisationFilter = model.Filter.Organisation;
prmOrganisation.RegionIdsIncluded.AddId(model.ExtractSysId(model.Filter.RegionId));
prmOrganisation.SportIdsIncluded.AddId(model.ExtractSysId(model.Filter.SportId));
prmOrganisation.StatusCat = SaxOrganisation.StatusCat.ActiveOnly;
prmOrganisation.SortColumns.Add(colOrganisationName);
model.Paging.ItemTotal = (ulong)AccountMgr.GetOrganisationCount(prmOrganisation);
model.PopulatePagingTo(prmOrganisation.ResultsPaging);
using (DbResultSet resultSet = AccountMgr.ExecuteOrganisationQuery(columns, prmOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
OrganisationListData organisationData = new OrganisationListData()
{
Id = model.EncryptId(organisationId),
Name = values.GetString(colOrganisationName.DataName),
RegionId = model.EncryptId(new SysId(values.GetLong(colRegionId.DataName))),
RegionName = values.GetString(colRegionName.DataName),
SportId = model.EncryptId(new SysId(values.GetLong(colSportId.DataName))),
SportName = values.GetString(colSportName.DataName),
};
// Logo.
if (values.GetLong(colLogoCount.DataName) > 0)
organisationData.LogoUrl = GetOrganisationLogoLink(organisationId, false);
// Add item to the list.
responseData.Organisations.Add(organisationData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Organisations.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Member Admin Organisation List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_MemberAdminOrganisationList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_MemberAdminOrganisationList(SaxModel_Online model) : base(model) { }
// Organisation list.
public List<OrganisationListData> Organisations { get; } = new List<OrganisationListData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetMemberAdminOrganisationList")]
public IActionResult Ajax_GetMemberAdminOrganisationList(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_GetMemberAdminOrganisationList";
String messageTitle = Localiser["organisation.caption.organisations"];
AjaxResponse_MemberAdminOrganisationList responseData = new AjaxResponse_MemberAdminOrganisationList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Query columns.
ColumnDfn colOrganisationId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Name);
ColumnDfn colRegionId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_RegionId);
ColumnDfn colRegionName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Region_Name);
ColumnDfn colSportId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_SportId);
ColumnDfn colSportName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Sport_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colOrganisationId, colOrganisationName,
colRegionId, colRegionName,
colSportId, colSportName
};
// Organisation filter
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationStatusCat = SaxOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.MemberAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.SortColumns.Add(colOrganisationName);
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserOrganisationCount(prmUserOrganisation);
model.PopulatePagingTo(prmUserOrganisation.ResultsPaging);
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(columns, prmUserOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
OrganisationListData organisationData = new OrganisationListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colOrganisationId.DataName))),
Name = values.GetString(colOrganisationName.DataName),
RegionId = model.EncryptId(new SysId(values.GetLong(colRegionId.DataName))),
RegionName = values.GetString(colRegionName.DataName),
SportId = model.EncryptId(new SysId(values.GetLong(colSportId.DataName))),
SportName = values.GetString(colSportName.DataName),
};
responseData.Organisations.Add(organisationData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Organisations.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Event Admin Organisation List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_EventAdminOrganisationList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_EventAdminOrganisationList(SaxModel_Online model) : base(model) { }
// Organisation list.
public List<OrganisationListData> Organisations { get; } = new List<OrganisationListData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetEventAdminOrganisationList")]
public IActionResult Ajax_GetEventAdminOrganisationList(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_GetEventAdminOrganisationList";
String messageTitle = Localiser["organisation.caption.organisations"];
AjaxResponse_EventAdminOrganisationList responseData = new AjaxResponse_EventAdminOrganisationList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Query columns.
ColumnDfn colOrganisationId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Name);
ColumnDfn colRegionId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_RegionId);
ColumnDfn colRegionName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Region_Name);
ColumnDfn colSportId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_SportId);
ColumnDfn colSportName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Sport_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colOrganisationId, colOrganisationName,
colRegionId, colRegionName,
colSportId, colSportName
};
// Organisation filter
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationStatusCat = SaxOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.ScheduleAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.SortColumns.Add(colOrganisationName);
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserOrganisationCount(prmUserOrganisation);
model.PopulatePagingTo(prmUserOrganisation.ResultsPaging);
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(columns, prmUserOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
OrganisationListData organisationData = new OrganisationListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colOrganisationId.DataName))),
Name = values.GetString(colOrganisationName.DataName),
RegionId = model.EncryptId(new SysId(values.GetLong(colRegionId.DataName))),
RegionName = values.GetString(colRegionName.DataName),
SportId = model.EncryptId(new SysId(values.GetLong(colSportId.DataName))),
SportName = values.GetString(colSportName.DataName),
};
responseData.Organisations.Add(organisationData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Organisations.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get Opportunity Admin Organisation List ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_OpportunityAdminOrganisationList : AjaxResponse_Data
{
// Construction.
public AjaxResponse_OpportunityAdminOrganisationList(SaxModel_Online model) : base(model) { }
// Organisation list.
public List<OrganisationListData> Organisations { get; } = new List<OrganisationListData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Organisation/Ajax/GetOpportunityAdminOrganisationList")]
public IActionResult Ajax_GetOpportunityAdminOrganisationList(Model_Organisation model)
{
String fn = GetType().Name + ".Ajax_GetOpportunityAdminOrganisationList";
String messageTitle = Localiser["organisation.caption.organisations"];
AjaxResponse_OpportunityAdminOrganisationList responseData = new AjaxResponse_OpportunityAdminOrganisationList(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Query columns.
ColumnDfn colOrganisationId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Name);
ColumnDfn colRegionId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_RegionId);
ColumnDfn colRegionName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Region_Name);
ColumnDfn colSportId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_SportId);
ColumnDfn colSportName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Sport_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colOrganisationId, colOrganisationName,
colRegionId, colRegionName,
colSportId, colSportName
};
// Organisation filter
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationStatusCat = SaxOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.OpportunityAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.SortColumns.Add(colOrganisationName);
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserOrganisationCount(prmUserOrganisation);
model.PopulatePagingTo(prmUserOrganisation.ResultsPaging);
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(columns, prmUserOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
OrganisationListData organisationData = new OrganisationListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colOrganisationId.DataName))),
Name = values.GetString(colOrganisationName.DataName),
RegionId = model.EncryptId(new SysId(values.GetLong(colRegionId.DataName))),
RegionName = values.GetString(colRegionName.DataName),
SportId = model.EncryptId(new SysId(values.GetLong(colSportId.DataName))),
SportName = values.GetString(colSportName.DataName),
};
responseData.Organisations.Add(organisationData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Organisations.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,193 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Organisation Controller.
//=======================================================================
public partial class OrganisationController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public OrganisationController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// No 'Index' view as yet - default to home page.
return RedirectToAction("", "");
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Members()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Model.
Model_Organisation_Members model = new Model_Organisation_Members(Localiser);
// Get a suitable organisation for the filter.
SysId filterOrganisationId = SessionData_Online.Filter.OrganisationIdMember;
String filterOrganisationCaption = SessionData_Online.Filter.OrganisationCaptionMember;
OrganisationData organisationData = GetOrganisationForFilter(filterOrganisationId, filterOrganisationCaption, Sax.OrganisationRole.MemberAdmin);
if (organisationData == null)
return RedirectToAction("", "");
// Make a note of the filter values.
model.Filter.OrganisationId = model.EncryptId(organisationData.Id);
model.Filter.OrganisationCaption = organisationData.Caption;
SessionData_Online.Filter.OrganisationIdMember = organisationData.Id;
SessionData_Online.Filter.OrganisationCaptionMember = organisationData.Caption;
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Events()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Model.
Model_Organisation_Events model = new Model_Organisation_Events(Localiser);
// Get a suitable organisation for the filter.
SysId filterOrganisationId = SessionData_Online.Filter.OrganisationIdMember;
String filterOrganisationCaption = SessionData_Online.Filter.OrganisationCaptionMember;
OrganisationData organisationData = GetOrganisationForFilter(filterOrganisationId, filterOrganisationCaption, Sax.OrganisationRole.ScheduleAdmin);
if (organisationData == null)
return RedirectToAction("", "");
// Make a note of the filter values.
model.Filter.OrganisationId = model.EncryptId(organisationData.Id);
model.Filter.OrganisationCaption = organisationData.Caption;
SessionData_Online.Filter.OrganisationIdMember = organisationData.Id;
SessionData_Online.Filter.OrganisationCaptionMember = organisationData.Caption;
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Opportunities()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Model.
Model_Organisation_Opportunities model = new Model_Organisation_Opportunities(Localiser);
// Get a suitable organisation for the filter.
SysId filterOrganisationId = SessionData_Online.Filter.OrganisationIdMember;
String filterOrganisationCaption = SessionData_Online.Filter.OrganisationCaptionMember;
OrganisationData organisationData = GetOrganisationForFilter(filterOrganisationId, filterOrganisationCaption, Sax.OrganisationRole.OpportunityAdmin);
if (organisationData == null)
return RedirectToAction("", "");
// Make a note of the filter values.
model.Filter.OrganisationId = model.EncryptId(organisationData.Id);
model.Filter.OrganisationCaption = organisationData.Caption;
SessionData_Online.Filter.OrganisationIdMember = organisationData.Id;
SessionData_Online.Filter.OrganisationCaptionMember = organisationData.Caption;
return View(model);
}
//-------------------------------------------------------------------
public class OrganisationData
{
public OrganisationData(SysId id) { Id = id; }
public SysId Id { get; }
public String Caption { get; set; }
}
private OrganisationData GetOrganisationForFilter(SysId filterOrganisationId, String filterOrganisationCaption, Sax.OrganisationRole organisationRole)
{
OrganisationData organisationData = null;
// Roles (any) required for organisation list.
List<Sax.OrganisationRole> requiredRoles = new List<Sax.OrganisationRole>() { Sax.OrganisationRole.Admin };
if (organisationRole > 0)
requiredRoles.Add(organisationRole);
// Filter to identify suitable organisations that user is a member of and has the appropriate rights.
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationStatusCat = SaxOrganisation.StatusCat.ActiveOnly;
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.UnionWith(requiredRoles);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
if (organisationRole == Sax.OrganisationRole.ScheduleAdmin)
{
prmUserOrganisation.OrganisationHasScheduledEvents = true;
}
// Restrict to specified Organisation.
if (SysId.IsValid(filterOrganisationId))
{
// Verify that the specified organisation is valid for the filter conditions.
prmUserOrganisation.OrganisationIdsIncluded.Add(filterOrganisationId);
if (AccountMgr.GetUserOrganisationCount(prmUserOrganisation) < 1)
// Specified organisation is not valid - remove restriction.
prmUserOrganisation.OrganisationIdsIncluded.Clear();
}
// Clear 'Scheduled Events' flag if no organisations available.
if (prmUserOrganisation.OrganisationHasScheduledEvents && AccountMgr.GetUserOrganisationCount(prmUserOrganisation) < 1)
prmUserOrganisation.OrganisationHasScheduledEvents = false;
// Pick the first valid Organisation.
ColumnDfn colOrganisationId = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.OrganisationId);
ColumnDfn colOrganisationName = AccountMgr.GetUserOrganisationColumnDfn(SaxUserOrganisation.Column.Organisation_Name);
prmUserOrganisation.SortColumns.Add(colOrganisationName);
prmUserOrganisation.ResultsPaging.PageLength = 1;
prmUserOrganisation.ResultsPaging.PageNo = 1;
using (DbResultSet resultSet = AccountMgr.ExecuteUserOrganisationQuery(new List<ColumnDfn>() { colOrganisationId, colOrganisationName }, prmUserOrganisation))
{
if (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId organisationId = new SysId(values.GetLong(colOrganisationId.DataName));
if (SysId.IsValid(organisationId))
{
organisationData = new OrganisationData(organisationId)
{
Caption = values.GetString(colOrganisationName.DataName)
};
}
}
}
return organisationData;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,43 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
public class PoliciesController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public PoliciesController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
Model_Policies model = new Model_Policies(Localiser);
return View(model);
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Privacy()
{
Model_Policies model = new Model_Policies(Localiser);
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Mvc;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Helpers;
using System;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Reference Controller - Contact Method Ajax actions only.
//=======================================================================
partial class ReferenceController
{
//-------------------------------------------------------------------
// Validate Contact Method.
//-------------------------------------------------------------------
private class AjaxResponse_ValidateContactMethod : AjaxResponse_Data
{
public AjaxResponse_ValidateContactMethod(SaxModel_Base model) : base(model) { }
}
[HttpPost]
[Route("Reference/Ajax/ValidateContactMethod")]
public IActionResult Ajax_ValidateContactMethod(Model_ContactMethod model)
{
String fn = GetType().Name + ".Ajax_ValidateContactMethod";
String messageTitle = Localiser["caption.contact.method"];
AjaxResponse_ValidateContactMethod responseData = new AjaxResponse_ValidateContactMethod(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
// Validate method.
Sax.ContactMethod method = EnumUtil.ParseEnum(model.ContactMethod, Sax.ContactMethod.Unknown);
if (model.ContactMethod.IsEmpty() || (int)method < 1)
responseData.SetControlError(nameof(model.ContactMethod), Localiser["msg.validation.mandatory.value"]);
// Validate method.
if (model.ContactValue.IsEmpty())
responseData.SetControlError(nameof(model.ContactValue), Localiser["msg.validation.mandatory.value"]);
// Method specific value validation.
if (!SaxContactMethodHelper.IsValidContactMethodValue(method, model.ContactValue))
{
// Log invalid values so that the validation can be adjusted if needed.
SaLogger.Warn(fn + " Invalid contact method value. Mathod: " + method.ToString() + ". Value: " + model.ContactValue);
responseData.SetControlError(nameof(model.ContactValue), Localiser["msg.validation.invalid.value"]);
}
if (responseData.HasControlErrors())
return Json(responseData);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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 Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Reference Controller - Ajax actions only.
//=======================================================================
public partial class ReferenceController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public ReferenceController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{}
//-------------------------------------------------------------------
// Sports
//-------------------------------------------------------------------
private class AjaxResponse_SportList : AjaxResponse_Base
{
public List<SaxModel_Online.SportData> Sports { get; } = new List<SaxModel_Online.SportData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Reference/Ajax/GetSports")]
public IActionResult Ajax_GetSportList(Model_Reference model)
{
String fn = GetType().Name + ".Ajax_GetSportList";
String messageTitle = Localiser["reference.caption.sports"];
AjaxResponse_SportList responseData = new AjaxResponse_SportList();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
// User doesn't have to be logged in, eg. to register organisation.
//if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
// Populate sports.
foreach (SaxModel_Online.SportData sportData in model.GetSportDataList_Active())
responseData.Sports.Add(sportData);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Regions
//-------------------------------------------------------------------
private class AjaxResponse_RegionList : AjaxResponse_Base
{
public List<SaxModel_Online.RegionData> Regions { get; } = new List<SaxModel_Online.RegionData>();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Reference/Ajax/GetRegions")]
public IActionResult Ajax_GetRegionList(Model_Reference model)
{
String fn = GetType().Name + ".Ajax_GetRegionList";
String messageTitle = Localiser["reference.caption.regions"];
AjaxResponse_RegionList responseData = new AjaxResponse_RegionList();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
// User doesn't have to be logged in to query regions, eg. for organisation request.
//if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
// Populate regions.
foreach (SaxModel_Online.RegionData regionData in model.GetRegionDataList_Active())
responseData.Regions.Add(regionData);
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,321 @@
using Newtonsoft.Json;
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.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Data;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
public class SaxController_Online : SaxController_Web
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Protected construction.
protected SaxController_Online(SaxLocaliser localiser, Microsoft.Extensions.Logging.ILogger<HomeController> logger) : base(localiser)
{
}
//-------------------------------------------------------------------
// Session data.
//-------------------------------------------------------------------
/// <summary>Online session data</summary>
protected SaxSessionData_Online SessionData_Online { get { return SessionData as SaxSessionData_Online; } }
protected override SaxSessionData DeserializeSessionData(String sessionDataJson)
{
return JsonConvert.DeserializeObject<SaxSessionData_Online>(sessionDataJson, SaxSessionDataJsonSerializerSettings);
}
// Security profile.
public bool UserIsJournalist { get { return SessionData != null && SessionData.User != null && SessionData.User.IsJournalist; } }
//-------------------------------------------------------------------
// User Session.
//-------------------------------------------------------------------
/// <summary>
/// Sign the account in.
/// <para>- Record login time and clear invalid login data.</para>
/// <para>- Open user session - store details against Sesion Data.</para>
/// <para>- Set 'Keep logged in' cookie if required.</para>
/// </summary>
protected bool SignUserIn(SaxUserAccount userAccount, List<SaxUserRole> userRoles, List<SaxUserConfig> userConfigs, bool keepLoggedIn, ref String errorMessage)
{
String fn = GetType().Name + ".SignUserIn";
// Validate User Account, just in case - should have been done before arriving here!
if (userAccount == null)
{
SaLogger.Error(fn + " Unable to sign account in - null user account.");
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
if (!userAccount.IsStatusActive)
{
SaLogger.Error(fn + " Unable to sign account in - user account is not active. Email: " + userAccount.EmailAddress);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
// 'Keep logged in' can be switched off by the application configuration.
keepLoggedIn = App.AppConfig.Security.SignIn.KeepLoggedIn.Allowed && keepLoggedIn;
// Log user in.
userAccount.KeepLoggedIn = keepLoggedIn;
userAccount.LastLoginAt = DateTime.Now;
userAccount.InvalidLoginCount = 0;
//userAccount.LastInvalidLoginAt = null;
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update account for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
errorMessage = Localiser["security.msg.signin.invalid"];
return false;
}
SaLogger.Info(fn + " User session opened for " + userAccount.EmailAddress, true);
// Create session.
OpenUserSession(SessionContext, userAccount, userRoles);
RefreshSessionProfileSports();
// Refresh User configuration.
foreach (SaxUserConfig userConfig in userConfigs)
{
switch (userConfig.ConfigType)
{
case Sax.UserConfigType.Online_Profile:
SessionData_Online.UserConfig.Profile = SaxConfigData.ProfileData.FromXml(userConfig.ConfigData);
break;
}
}
// Send the 'Keep Logged In' cookie if requested.
if (userAccount.KeepLoggedIn)
SetKeepLoggedInCookie();
return true;
}
//-------------------------------------------------------------------
/// <summary>Populate the user's profile contact details to the session.</summary>
protected bool RefreshSessionProfileContact(SaxUserAccount userAccount)
{
String fn = GetType().Name + ".RefreshSessionProfileContact";
SaxSessionData_Online sessionData = SessionData_Online;
SysId userAccountId = sessionData?.User.UserAccountId ?? null;
if (!SysId.IsValid(userAccountId)) return false;
if (!SysId.IsValid(userAccount?.Id ?? null)) return false;
if (!userAccountId.Equals(userAccount.Id)) return false;
sessionData.RefreshUser(userAccount);
return true;
}
//-------------------------------------------------------------------
/// <summary>Populate the user's profile sports to the session.</summary>
protected bool RefreshSessionProfileSports()
{
String fn = GetType().Name + ".RefreshSessionProfileSports";
SaxSessionData_Online sessionData = SessionData_Online;
SysId userAccountId = sessionData?.User.UserAccountId ?? null;
if (!SysId.IsValid(userAccountId)) return false;
sessionData.Profile.ClearSports();
// Read discrete values to avoid loading the entire UserSport records which can be substantial.
ColumnDfn colSportId = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportId);
ColumnDfn colSportName = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Sport_Name);
ColumnDfn colParticipating = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportParticipating);
ColumnDfn colPreparing = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportPreparing);
ColumnDfn colOfficiating = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportOfficiating);
ColumnDfn colStatus = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Status);
SaxUserSport.Params prmUserSport = new SaxUserSport.Params();
prmUserSport.UserAccountIdsIncluded.Add(userAccountId);
prmUserSport.StatusCat = SaxUserSport.StatusCat.ActiveOnly;
using (DbResultSet resultSet = AccountMgr.ExecuteUserSportQuery(new List<ColumnDfn>() { colSportId, colSportName, colParticipating, colOfficiating, colStatus }, prmUserSport))
{
while (resultSet != null && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
String sportName = values.GetString(colSportName.DataName);
Sax.SportParticipating participating = (Sax.SportParticipating)values.GetLong(colParticipating.DataName);
Sax.SportPreparing preparing = (Sax.SportPreparing)values.GetLong(colPreparing.DataName);
Sax.SportOfficiating officiating = (Sax.SportOfficiating)values.GetLong(colOfficiating.DataName);
SaStatus status = SacsSystem.GetStatusForCode(values.GetString(colStatus.DataName));
sessionData.Profile.AddSport(sportId, status);
}
}
SaLogger.Debug(fn + " Profile Sports: " + sessionData.Profile.SportCount);
return true;
}
//-------------------------------------------------------------------
/// <summary>Start a new user session on sign-in.</summary>
protected bool OpenUserSession(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles)
{
// Validate user properties.
if (!SysId.IsValid(userAccount.Id)) return false;
if (StringUtil.IsEmpty(userAccount.EmailAddress)) return false;
// Close previous session.
CloseUserSession();
// Open session.
return SetSessionData(new SaxSessionData_Online(sessionContext, userAccount, userRoles));
}
protected void CloseUserSession()
{
ClearSessionData();
}
//-------------------------------------------------------------------
// Image utilities.
//-------------------------------------------------------------------
/// <summary>
/// Construct URL to display Organisation logo.
/// <para>- Option to allow for 'Upload' image if active.</para>
/// </summary>
public String GetOrganisationLogoLink(SysId organisationId, bool uploadImage)
{
String imageLink = "Image/Organisation/Logo" + (uploadImage ? "/Upload" : "") + "?";
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ImageController.ParamName_OrganisationId, SysId.ToString(organisationId));
return imageLink + ImageController.ParamName_OrganisationLogoEncrypted + "=" + EncryptOnlineParams(parameters);
}
/// <summary>
/// Construct URL to display specific item image.
/// </summary>
public String GetItemImageLink(SysId itemImageId)
{
String imageLink = "Image/Item?";
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ImageController.ParamName_ItemImageId, SysId.ToString(itemImageId));
return imageLink + ImageController.ParamName_ItemImageEncrypted + "=" + EncryptOnlineParams(parameters);
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
/// <summary>Keep-Logged-In cookie.</summary>
protected void SetKeepLoggedInCookie()
{
String keepLoggedInCookieValue = GenerateKeepLoggedInCookieValue();
int cookieExpiresDays = Math.Max(Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Min, Math.Min(Sax.Config.Security.SignIn.KeepLoggedIn.CookieExpiresDays_Max, AppConfig.Security.SignIn.KeepLoggedIn.CookieExpiresDays));
SetResponseCookie(KeepLoggedIn.CookieName, keepLoggedInCookieValue, DateTime.Now.AddDays(cookieExpiresDays), true);
SaLogger.Info(this.GetType().Name + ".SetKeepLoggedInCookie 'Keep Logged In' cookie set. Value: " + keepLoggedInCookieValue);
}
/// <summary>Set the 'Keep Logged In' response cookie to expired with no data.</summary>
public void ClearKeepLoggedInCookie()
{
if (StringUtil.IsEmpty(GetRequestCookie(KeepLoggedIn.CookieName))) return;
// Clear cookie by setting expiry date in the past.
SetResponseCookie(KeepLoggedIn.CookieName, "", DateTime.Today.AddDays(-7), true);
SaLogger.Info($"{SysUtil.Fn} 'Keep Logged In' cookie cleared.");
}
//-------------------------------------------------------------------
// 'Keep Logged In' cookie and encryption config.
// NB - IV value is generated from the key and fixed so that it doesn't change when the application restarts
// to ensure that cookie value can be decrypted even if application restarts in the meantime.
// - Generating IV from same key results in different value each time.
// - IV Must be regenerated if key is changed: uncomment 'Generated' variable and copy resulting value to IV variable.
// NB - 'Keep Logged In' is termed 'Remember Me' in the Online application.
public static class KeepLoggedIn
{
public static String CookieName = "SAXRM";
public static String Base64Key = SacsEncryptionHelper.EncodePlainKey("<=Saxonite^OnlIne{R$Member-m3}~>", SacsEncryptionHelper.KeyLength);
public const String Purpose = "Saxonite.Online.KeepLoggedIn";
}
//-------------------------------------------------------------------
// Keep-logged-in cookie value.
private static readonly String ParamName_KeepLoggedIn_UserAccountId = "uai";
private String GenerateKeepLoggedInCookieValue()
{
NameValueCollection keepLoggedInParams = new NameValueCollection();
keepLoggedInParams.Add(ParamName_KeepLoggedIn_UserAccountId, SysId.ToString(SessionData_Online.User.UserAccountId));
return EncryptParameters(keepLoggedInParams, KeepLoggedIn.Base64Key, KeepLoggedIn.Purpose);
}
//-------------------------------------------------------------------
public class KeepLoggedInParams
{
public SysId UserAccountId { get; set; }
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
public List<String> GetValidParams()
{
List<String> values = new List<String>();
if (SysId.IsValid(UserAccountId))
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
return values;
}
}
public KeepLoggedInParams ExtractKeepLoggedInParams(String encryptedParams)
{
KeepLoggedInParams keepLoggedInParams = new KeepLoggedInParams();
if (!StringUtil.IsEmpty(encryptedParams))
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, KeepLoggedIn.Base64Key, KeepLoggedIn.Purpose);
keepLoggedInParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_KeepLoggedIn_UserAccountId)));
}
if (DebugMode) SaLogger.Info($"{SysUtil.Fn} UserAccountId: {SysId.ToString(keepLoggedInParams.UserAccountId)}");
return keepLoggedInParams;
}
//-------------------------------------------------------------------
// Parameter encryption, eg. for images.
//-------------------------------------------------------------------
// Purpose-bound AES-GCM envelopes use a fresh nonce for every encrypted value.
public static String Base64Key_OnlineApp { get; } = SacsEncryptionHelper.EncodePlainKey("Saxonite#Online@Encrypt+" + DateTime.Today.Format("ddMMyyyy"), SacsEncryptionHelper.KeyLength);
private const String Purpose_OnlineApp = "Saxonite.Online.Parameters";
protected String EncryptOnlineParams(NameValueCollection parameters)
{
return HttpUtility.UrlEncode(EncryptParameters(parameters, Base64Key_OnlineApp, Purpose_OnlineApp));
}
protected NameValueCollection ExtractOnlineParams(String encryptedParams)
{
return DecryptParameters(encryptedParams, Base64Key_OnlineApp, Purpose_OnlineApp);
}
//-------------------------------------------------------------------
// Logging.
//-------------------------------------------------------------------
/// <summary>
/// Log suffix:
/// <para>- User's email address.</para>
/// <para>- Prefixed with space.</para>
/// </summary>
/// <returns></returns>
protected String GetLogSuffix()
{
List<String> logSuffix = new List<string>();
SaxSessionData_Online sessionData = SessionData_Online;
if (sessionData != null)
{
if (!StringUtil.IsEmpty(sessionData.User.EmailAddress))
logSuffix.Add("User: " + sessionData.User.EmailAddress);
}
return logSuffix.Concat("", " ", ".");
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,403 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Core.Web.Online.Helpers;
using Org.BouncyCastle.Security;
using static Saxonite.Net.Standard.Base.App.Sax;
using System.Text.Json.Serialization;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Search Controller - Organisations - Ajax actions.
//=======================================================================
partial class SearchController
{
//-------------------------------------------------------------------
// Account - Get Organisations.
//-------------------------------------------------------------------
private class OrganisationListData : AjaxResponse_Data.ItemData_Base
{
public String Name { get; set; }
public String CategoryDesc { get; set; }
public String SportName { get; set; }
public String RegionName { get; set; }
public String LogoUrl { get; set; }
}
private class AjaxResponse_Search_Organisations : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Search_Organisations(SaxModel_Online model) : base(model) { }
// Organisation list.
public List<OrganisationListData> Organisations { get; } = new List<OrganisationListData>();
}
// Ajax request.
[HttpPost]
[Route("Search/Ajax/Organisations")]
[Route("Search/Ajax/GetOrganisations")]
public IActionResult Ajax_GetOrganisations(Model_Search_Organisations model)
{
String fn = GetType().Name + ".Ajax_GetOrganisations";
AjaxResponse_Search_Organisations responseData = new AjaxResponse_Search_Organisations(model);
String messageTitle = Localiser["organisation.caption.organisations"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Ensure data is paged to avoid leaking large volumes.
model.Paging.PageNo = Math.Max(1, model.Paging.PageNo);
model.Paging.PageLength = Math.Max(Math.Min(Sax.Config.Online.Search.Organisations.PageLengthMax, model.Paging.PageLength), (ushort)1);
// Query organisations that user is a member of.
SaxOrganisation.Params prmOrganisation = new SaxOrganisation.Params();
prmOrganisation.OrganisationFilter = model.Filter.Organisation;
prmOrganisation.OrganisationType = model.FilterOrganisationType;
prmOrganisation.OrganisationCategory = model.FilterOrganisationCategory;
prmOrganisation.OrganisationCategoriesExcluded.Add(Sax.OrganisationCategory.Partner);
prmOrganisation.OrganisationCategoriesExcluded.Add(Sax.OrganisationCategory.Other);
if (SysId.IsValid(model.FilterSportId))
prmOrganisation.SportIdsIncluded.Add(model.FilterSportId);
if (SysId.IsValid(model.FilterRegionId))
prmOrganisation.RegionIdsIncluded.Add(model.FilterRegionId);
prmOrganisation.StatusCat = SaxOrganisation.StatusCat.ActiveOnly;
model.Paging.ItemTotal = (ulong)AccountMgr.GetOrganisationCount(prmOrganisation);
model.PopulatePagingTo(prmOrganisation.ResultsPaging);
List<SaxOrganisation> organisations = AccountMgr.GetOrganisationList(prmOrganisation);
List<SysId> organisationIds = organisations.GetIds();
// Item columns.
ColumnDfn colId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Id);
ColumnDfn colName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name);
ColumnDfn colCategory = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationCategory_Desc);
ColumnDfn colSportId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.SportId);
ColumnDfn colSportName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Sport_Name);
ColumnDfn colRegionId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.RegionId);
ColumnDfn colRegionName = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Region_Name);
ColumnDfn colLogoCount = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.LogoImage_Count);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colName, colCategory,
colSportId, colSportName,
colRegionId, colRegionName,
colLogoCount
};
// Sort columns.
colName.SortAscending = true;
prmOrganisation.SortColumns.Add(colName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteOrganisationQuery(columns, prmOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId organisationId = new SysId(values.GetLong(colId.DataName));
Sax.OrganisationCategory category = (Sax.OrganisationCategory)OrganisationCategoryCodes.GetTypeForCode(values.GetString(colCategory.DataName));
OrganisationListData organisationData = new OrganisationListData()
{
Id = model.EncryptId(organisationId),
Name = values.GetString(colName.DataName),
CategoryDesc = Localiser.GetOrganisationCategoryCaption(category),
SportName = values.GetString(colSportName.DataName),
RegionName = values.GetString(colRegionName.DataName),
};
// Logo.
if (values.GetLong(colLogoCount.DataName) > 0)
organisationData.LogoUrl = GetOrganisationLogoLink(organisationId, false);
// Add item to the list.
responseData.Organisations.Add(organisationData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Organisations.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Get Organisation.
//-------------------------------------------------------------------
/// <summary>
/// Associations are grouped by type and direction (parent->child).
/// <br>The organisation specified in each case is the 'other' organisation (not the current 'target' organisation).</br>
/// </summary>
private class AssociationTypeData
{
public String Caption { get; set; }
public List<AssociationOrganisationData> Organisations { get; } = new List<AssociationOrganisationData>();
}
/// <summary>
/// Association Organisation - the one that is not the current 'target' organisation.
/// <br>Region and Sport are only populate if different from the 'target' organisation.</br>
/// </summary>
private class AssociationOrganisationData
{
[JsonIgnore]
public SysId OrganisationId { get; set; }
public String Id { get; set; }
public String Name { get; set; }
public String Display
{
get
{
String display = Name;
if (!StringUtil.IsBlank(Sport) || !StringUtil.IsBlank(Region))
display += " (" + String.Join(", ", new List<String> { Sport, Region }.Where(item => !StringUtil.IsBlank(item))) + ")";
return display;
}
}
public String Region { get; set; }
public String Sport { get; set; }
public String LogoUrl { get; set; }
}
private class OrganisationItemData : OrganisationListData
{
public String TypeDesc { get; set; }
public String About { get; set; }
public String Facilities { get; set; }
public List<AjaxResponse_Data.ItemLabelValue> Contacts { get; set; }
public String Address { get; set; }
public String PostalCode { get; set; }
public List<AssociationTypeData> Associations { get; } = new List<AssociationTypeData>();
}
private class AjaxResponse_Search_Organisation : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Search_Organisation(SaxModel_Online model) : base(model) { }
// Organisation list.
public OrganisationItemData Organisation { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Search/Ajax/Organisation")]
[Route("Search/Ajax/GetOrganisation")]
public IActionResult Ajax_GetOrganisation(Model_Search_Organisations model)
{
String fn = GetType().Name + ".Ajax_GetOrganisation";
AjaxResponse_Search_Organisation responseData = new AjaxResponse_Search_Organisation(model);
String messageTitle = Localiser["label.organisation"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve organisation.
SysId organisationId = model.ExtractSysId(model.OrganisationId);
if (!SysId.IsValid(organisationId))
{
SaLogger.Warn(fn + " Organisation Id not supplied.");
return Json(responseData);
}
SaxOrganisation organisation = AccountMgr.GetOrganisationById(organisationId);
if (organisation == null)
{
SaLogger.Warn(fn + " Failed to retrieve organisation. Id: " + model.OrganisationId);
return Json(responseData);
}
disposableItems.Add(organisation);
// ToDo May-2022 TESTING.
//SaLogger.Info(fn + " Organisation: " + organisation.Name + "\nAbout: " + organisation.About + "\nFacilities: " + organisation.Facilities, true);
// Populate response.
OrganisationItemData organisationData = new OrganisationItemData()
{
Id = model.EncryptId(organisation.Id),
Name = organisation.Name,
CategoryDesc = Localiser.GetOrganisationCategoryCaption(organisation.OrganisationCategory),
TypeDesc = Localiser.GetOrganisationTypeCaption(organisation.OrganisationType),
SportName = model.GetSportCaption(organisation.SportIdPrimary),
RegionName = model.GetRegionCaption(organisation.RegionIdPrimary),
About = organisation.About,
Facilities = organisation.Facilities,
};
responseData.Organisation = organisationData;
// Contacts.
SaxContactData contactData = new SaxContactData(organisation.ContactData);
if (contactData.Methods.Count > 0)
{
responseData.Organisation.Contacts = new List<AjaxResponse_Data.ItemLabelValue>();
foreach (Sax.ContactMethod method in contactData.Methods)
{
AjaxResponse_Data.ItemLabelValue contact = new AjaxResponse_Data.ItemLabelValue()
{
Label = Localiser.GetContactMethodCaption(method),
Value = SaxContactMethodHelper_Online.GetContactMethodHtml(method, contactData.GetContactMethodValue(method), Localiser)
};
responseData.Organisation.Contacts.Add(contact);
}
}
// Organisation address.
SaxItemAddress address = ItemDataMgr.GetFirstItemAddressByItemAndType(organisation.GetItemId());
if (address != null)
{
String addressLines = (new List<string>() { address.Line1, address.Line2, address.Line3 }).Concat(", ", "", "", true, true);
String addressTownProvince = (new List<string>() { address.Town, address.Province }).Concat(", ", "", "", true, true);
organisationData.Address = (new List<String>() { addressLines, addressTownProvince }).Concat("\n", "", "", true, true);
organisationData.PostalCode = address.PostalCode;
}
// Associations.
Dictionary<SaxItemAssociation.TypeOptionData, List<AssociationOrganisationData>> associationTypeGroups = new Dictionary<SaxItemAssociation.TypeOptionData, List<AssociationOrganisationData>>();
ColumnDfn colId = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.Id);
ColumnDfn colAssociationType = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.AssociationType);
ColumnDfn colItemTypeFrom = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.ItemTypeFrom);
ColumnDfn colItemIdFrom = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.ItemIdFrom);
ColumnDfn colOrganisationFrom_Name = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.OrganisationFrom_Name);
ColumnDfn colOrganisationFrom_SportIdPrimary = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.OrganisationFrom_SportIdPrimary);
ColumnDfn colOrganisationFrom_RegionIdPrimary = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.OrganisationFrom_RegionIdPrimary);
ColumnDfn colItemTypeTo = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.ItemTypeTo);
ColumnDfn colItemIdTo = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.ItemIdTo);
ColumnDfn colOrganisationTo_Name = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.OrganisationTo_Name);
ColumnDfn colOrganisationTo_SportIdPrimary = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.OrganisationTo_SportIdPrimary);
ColumnDfn colOrganisationTo_RegionIdPrimary = ItemDataMgr.GetItemAssociationColumnDfn(SaxItemAssociation.Column.OrganisationTo_RegionIdPrimary);
List<ColumnDfn> associationCols = new List<ColumnDfn>()
{
colId, colAssociationType,
colItemTypeFrom, colItemIdFrom, colOrganisationFrom_Name, colOrganisationFrom_SportIdPrimary, colOrganisationFrom_RegionIdPrimary,
colItemTypeTo, colItemIdTo, colOrganisationTo_Name, colOrganisationTo_SportIdPrimary, colOrganisationTo_RegionIdPrimary
};
List<SysId> assocOrganisationIds = new List<SysId>();
SaxItemAssociation.Params prmAssociations = new SaxItemAssociation.Params();
prmAssociations.ItemIdsIncluded.Add(organisation.GetItemId());
prmAssociations.StatusCat = SaxItemAssociation.StatusCat.ActiveOnly;
using (DbResultSet resultSet = ItemDataMgr.ExecuteItemAssociationQuery(associationCols, prmAssociations))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId associationId = new SysId(values.GetLong(colId.DataName));
Sax.AssociationType associationType = (Sax.AssociationType)values.GetLong(colAssociationType.DataName);
ItemId itemIdFrom = new ItemId((short)values.GetLong(colItemTypeFrom.DataName), new SysId(values.GetLong(colItemIdFrom.DataName)));
ItemId itemIdTo = new ItemId((short)values.GetLong(colItemTypeTo.DataName), new SysId(values.GetLong(colItemIdTo.DataName)));
bool associationTypeDirectionReversed = associationType == Sax.AssociationType.Affiliate && itemIdTo.Id.Equals(organisation.Id);
// Determine association type group.
SaxItemAssociation.TypeOptionData assocTypeOption = new SaxItemAssociation.TypeOptionData(associationType, associationTypeDirectionReversed, GetAssociationTypeOptionCaption(associationType, associationTypeDirectionReversed));
List<AssociationOrganisationData> organisations = associationTypeGroups.GetValue(assocTypeOption, null);
if (organisations == null)
associationTypeGroups.Add(assocTypeOption, organisations = new List<AssociationOrganisationData>());
// Populate organisation properties.
SysId orgSportIdFrom = new SysId(values.GetLong(colOrganisationFrom_SportIdPrimary.DataName));
SysId orgRegionIdFrom = new SysId(values.GetLong(colOrganisationFrom_RegionIdPrimary.DataName));
SysId orgSportIdTo = new SysId(values.GetLong(colOrganisationTo_SportIdPrimary.DataName));
SysId orgRegionIdTo = new SysId(values.GetLong(colOrganisationTo_RegionIdPrimary.DataName));
if (itemIdTo.Id.Equals(organisation.Id))
{
// 'From' item is the 'Other' organisation.
AssociationOrganisationData assocOrgData = new AssociationOrganisationData();
assocOrgData.OrganisationId = itemIdFrom.Id;
assocOrganisationIds.AddId(assocOrgData.OrganisationId);
assocOrgData.Id = model.EncryptId(assocOrgData.OrganisationId);
assocOrgData.Name = values.GetString(colOrganisationFrom_Name.DataName);
if (!orgSportIdFrom.Equals(orgSportIdTo))
assocOrgData.Sport = model.GetSportCaption(orgSportIdFrom);
if (!orgRegionIdFrom.Equals(orgRegionIdTo))
assocOrgData.Region = model.GetRegionCaption(orgRegionIdFrom);
organisations.Add(assocOrgData);
}
else
{
// 'To' item is the 'Other' organisation.
AssociationOrganisationData assocOrgData = new AssociationOrganisationData();
assocOrgData.OrganisationId = itemIdTo.Id;
assocOrganisationIds.AddId(assocOrgData.OrganisationId);
assocOrgData.Id = model.EncryptId(assocOrgData.OrganisationId);
assocOrgData.Name = values.GetString(colOrganisationTo_Name.DataName);
if (!orgSportIdFrom.Equals(orgSportIdTo))
assocOrgData.Sport = model.GetSportCaption(orgSportIdTo);
if (!orgRegionIdFrom.Equals(orgRegionIdTo))
assocOrgData.Region = model.GetRegionCaption(orgRegionIdTo);
organisations.Add(assocOrgData);
}
}
}
// Populate Associated Orgaisation Logo URLs.
if (assocOrganisationIds.Any())
{
ColumnDfn colAssocOrganisation_Id = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Id);
ColumnDfn colAssocOrganisation_LogoCount = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.LogoImage_Count);
List<ColumnDfn> columns = new List<ColumnDfn>() { colAssocOrganisation_Id, colAssocOrganisation_LogoCount };
SaxOrganisation.Params prmAssocOrganisation = new SaxOrganisation.Params();
prmAssocOrganisation.IdsIncluded.AddIds(assocOrganisationIds);
using (DbResultSet resultSet = AccountMgr.ExecuteOrganisationQuery(columns, prmAssocOrganisation))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId assocOrganisationId = new SysId(values.GetLong(colAssocOrganisation_Id.DataName));
if (values.GetLong(colAssocOrganisation_LogoCount.DataName) > 0)
{
foreach (List<AssociationOrganisationData> assocOrganisations in associationTypeGroups.Values)
{
foreach (AssociationOrganisationData assocOrganisation in assocOrganisations.Where(item => item.OrganisationId.Equals(assocOrganisationId)))
assocOrganisation.LogoUrl = GetOrganisationLogoLink(assocOrganisation.OrganisationId, false);
}
}
}
}
}
// Populate associations to response.
foreach (SaxItemAssociation.TypeOptionData associationTypeGroup in associationTypeGroups.Keys.OrderBy(item => (int)item.TypeOption))
{
AssociationTypeData associationTypeData = new AssociationTypeData();
associationTypeData.Caption = associationTypeGroup.Caption;
associationTypeData.Organisations.AddRange(associationTypeGroups.GetValue(associationTypeGroup, new List<AssociationOrganisationData>()).OrderBy(item => item.Name).ToList());
responseData.Organisation.Associations.Add(associationTypeData);
}
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
/// <summary>
/// Caption to display above list of corresponding associated organisation for the 'target' organisation.
/// </summary>
private String GetAssociationTypeOptionCaption(Sax.AssociationType associationType, bool reversed)
{
switch (SaxItemAssociation.GetAssociationTypeOption(associationType, reversed))
{
case AssociationTypeOption.AffiliateParent: return "Affiliated to";
case AssociationTypeOption.AffiliateChild: return "Affiliates";
case AssociationTypeOption.Partner: return "Partners";
}
return "Unknown relationship";
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,194 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Search Controller - Users - Ajax actions.
//=======================================================================
partial class SearchController
{
//-------------------------------------------------------------------
// Account - Get Users.
//-------------------------------------------------------------------
private class UserListData
{
public String Id { get; set; }
public String Name { get; set; }
public String Location { get; set; }
}
private class AjaxResponse_Search_Users : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Search_Users(SaxModel_Online model) : base(model) { }
// User list.
public List<UserListData> Users { get; } = new List<UserListData>();
}
// Ajax request.
[HttpPost]
[Route("Search/Ajax/Users")]
[Route("Search/Ajax/GetUsers")]
public IActionResult Ajax_GetUsers(Model_Search_Users model)
{
String fn = GetType().Name + ".Ajax_GetUsers";
AjaxResponse_Search_Users responseData = new AjaxResponse_Search_Users(model);
String messageTitle = Localiser["label.users"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Ensure data is paged to avoid leaking large volumes.
model.Paging.PageNo = Math.Max(1, model.Paging.PageNo);
model.Paging.PageLength = Math.Max(Math.Min(Sax.Config.Online.Search.Users.PageLengthMax, model.Paging.PageLength), (ushort)1);
// Ensure filter has something to restrict the list.
// ToDo May be able to show some results, such as friends when no filter provided.
if (StringUtil.IsEmpty(StringUtil.TrimAll(model.Filter.User)))
{
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
// Query users that user is a member of.
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
prmUserAccount.UserFilter = model.Filter.User;
if (SysId.IsValid(model.FilterSportId))
prmUserAccount.SportIdsIncluded.Add(model.FilterSportId);
if (SysId.IsValid(model.FilterRegionId))
prmUserAccount.RegionIdsIncluded.Add(model.FilterRegionId);
prmUserAccount.StatusCat = SaxUserAccount.StatusCat.ActiveOnly;
model.Paging.ItemTotal = (ulong)AccountMgr.GetUserAccountCount(prmUserAccount);
model.PopulatePagingTo(prmUserAccount.ResultsPaging);
List<SaxUserAccount> users = AccountMgr.GetUserAccountList(prmUserAccount);
List<SysId> userIds = users.GetIds();
// Item columns.
ColumnDfn colId = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Id);
ColumnDfn colTitle = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Title);
ColumnDfn colFirstName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.FirstName);
ColumnDfn colLastName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastName);
ColumnDfn colRegionCountryName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.RegionCountry_Name);
ColumnDfn colRegionProvinceName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.RegionProvince_Name);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId, colTitle, colFirstName, colLastName,
colRegionCountryName,colRegionProvinceName,
};
// Sort columns.
colLastName.SortAscending = true;
prmUserAccount.SortColumns.Add(colLastName);
// Query items.
using (DbResultSet resultSet = AccountMgr.ExecuteUserAccountQuery(columns, prmUserAccount))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
String displayName = SaxUserAccount.GetDisplayName(values.GetString(colTitle.DataName), values.GetString(colFirstName.DataName), values.GetString(colLastName.DataName));
List<String> location = new List<string>() { values.GetString(colRegionProvinceName.DataName), values.GetString(colRegionCountryName.DataName) };
UserListData userData = new UserListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
Name = displayName,
Location = location.Concat(", ", "", "", true, true),
};
responseData.Users.Add(userData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Users.Count;
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Account - Get Users.
//-------------------------------------------------------------------
private class UserData
{
public String Id { get; set; }
public String Name { get; set; }
public String Location { get; set; }
}
private class AjaxResponse_Search_User : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Search_User(SaxModel_Online model) : base(model) { }
// User list.
public UserData User { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Search/Ajax/User")]
[Route("Search/Ajax/GetUser")]
public IActionResult Ajax_GetUser(Model_Search_Users model)
{
String fn = GetType().Name + ".Ajax_GetUser";
AjaxResponse_Search_User responseData = new AjaxResponse_Search_User(model);
String messageTitle = Localiser["label.user"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Retrieve user.
SysId userAccountId = model.ExtractSysId(model.UserId);
if (!SysId.IsValid(userAccountId))
{
SaLogger.Warn(fn + " User Id not supplied.");
return Json(responseData);
}
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve user. Id: " + model.UserId);
return Json(responseData);
}
disposableItems.Add(userAccount);
// Populate response.
List<String> location = new List<string>() { model.GetRegionCaption(userAccount.RegionIdProvince), model.GetRegionCaption(userAccount.RegionIdCountry) };
UserData userData = new UserData()
{
Id = model.EncryptId(userAccount.Id),
Name = userAccount.GetDisplayName(),
Location = location.Concat(", ", "", "", true, true),
};
responseData.User = userData;
}
// Complete response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Search Controller.
//=======================================================================
public partial class SearchController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public SearchController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// No 'Index' view as yet - default to home page.
return RedirectToAction("", "");
}
//-------------------------------------------------------------------
/// <summary>
/// Search - Users.
/// </summary>
[HttpGet]
public IActionResult Users()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Model.
Model_Search_Users model = new Model_Search_Users(Localiser);
model.Paging.PageNo = 1;
model.Paging.PageLength = Sax.Config.Online.Search.Organisations.PageLengthDefault;
return View(model);
}
//-------------------------------------------------------------------
/// <summary>
/// Search - Organisations.
/// </summary>
[HttpGet]
public IActionResult Organisations()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// Model.
Model_Search_Organisations model = new Model_Search_Organisations(Localiser);
model.Paging.PageNo = 1;
model.Paging.PageLength = Sax.Config.Online.Search.Users.PageLengthDefault;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,459 @@
using Microsoft.AspNetCore.Mvc;
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.Email;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Security Controller - Ajax actions.
//=======================================================================
partial class SecurityController
{
//-------------------------------------------------------------------
// Sign-in request.
//-------------------------------------------------------------------
private class AjaxResponse_Signin : AjaxResponse_Data
{
// Construction.
public AjaxResponse_Signin(SaxModel_Online model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/Signin")]
public IActionResult Ajax_Signin(Model_Security model)
{
String fn = GetType().Name + ".Ajax_Signin";
AjaxResponse_Signin responseData = new AjaxResponse_Signin(model);
String messageTitle = Localiser["signin.caption"];
// Validate referrer.
if (!ValidReferer) return Forbid();
// Validate Sign-in data.
model.ValidateSignInData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Get user account.
SaxUserAccount userAccount = GetUserAccountForSignin(model, responseData);
disposableItems.Add(userAccount);
if (responseData.HasControlErrors())
return Json(responseData);
List<SaxUserRole> userRoles = AccountMgr.GetRolesForUser(userAccount.Id);
disposableItems.AddRange(userRoles);
List<SaxUserConfig> userConfigs = AccountMgr.GetConfigsForUser(userAccount.Id);
disposableItems.AddRange(userConfigs);
String errorMessage = "";
if (!SignUserIn(userAccount, userRoles, userConfigs, model.KeepMeSignedIn, ref errorMessage))
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
}
// Complete response.
responseData.Success = SessionData_Online?.ValidUserSession ?? false;
return Json(responseData);
}
//-------------------------------------------------------------------
// Keep Logged In.
//-------------------------------------------------------------------
private class AjaxResponse_KeepLoggedIn : AjaxResponse_Base
{
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/KeepLoggedIn")]
public ActionResult Ajax_KeepLoggedIn(Model_Security model)
{
String fn = GetType().Name + ".Ajax_KeepLoggedIn";
if (DebugMode) SaLogger.Debug(fn);
AjaxResponse_KeepLoggedIn responseData = new AjaxResponse_KeepLoggedIn();
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return Json(responseData.SetGoHome());
RefreshModel(model);
if (!AppConfig.Security.SignIn.KeepLoggedIn.Allowed)
return Json(responseData);
// Resend the cookie to extend the expiry date if it is currently populated.
if (!StringUtil.IsEmpty(GetRequestCookie(KeepLoggedIn.CookieName)))
SetKeepLoggedInCookie();
// Construct response.
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
public SaxUserAccount GetUserAccountForSignin(Model_Security model, AjaxResponse_Data responseData)
{
String fn = "SigninController.GetUserAccountForSignin";
// Identify user account.
SaxUserAccount userAccount = null;
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
prmUserAccount.EmailAddress = model.EmailAddress;
prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any;
foreach (SaxUserAccount userAccountDuplicate in AccountMgr.GetUserAccountList(prmUserAccount))
{
// Account must be active to allow sign-in.
// NB Debug testing allows 'Awaiting' accounts to sign-in.
if ((SaxSystem.DebugMode && userAccountDuplicate.Status == SaStatus.Awaiting) || userAccountDuplicate.IsStatusActive)
{
userAccount = userAccountDuplicate;
break;
}
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with status " + SaxSystem.GetStatusDesc(userAccount.Status), true);
}
if (userAccount != null)
{
if (!SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password) && !(DebugMode && model.EmailAddress.EqualsCI("andy@sarand.com") && model.Password.EqualsCI("SolumForti")))
{
// Record invalid login attempt.
userAccount.InvalidLoginCount++;
userAccount.LastInvalidLoginAt = DateTime.Now;
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
SaLogger.Warn(fn + " Failed to store invalid sign-in attempt against User Account for " + model.EmailAddress + ": " + AccountMgr.ErrorStateMessage, true);
SaLogger.Warn(fn + " Attempt to sign-in to User Account for " + model.EmailAddress + " with invalid password. Attempt: " + userAccount.InvalidLoginCount, true);
userAccount = null;
}
}
if (userAccount == null && !responseData.HasControlErrors())
responseData.SetControlError(nameof(model.EmailAddress), model.Localiser["security.msg.signin.invalid"]);
return userAccount;
}
//-------------------------------------------------------------------
// Sign-out request.
//-------------------------------------------------------------------
private class AjaxResponse_SignOut : AjaxResponse_Data
{
// Construction.
public AjaxResponse_SignOut(SaxModel_Online model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/SignOut")]
public ActionResult Ajax_SignOut(Model_Security model)
{
String fn = GetType().Name + ".Ajax_SignOut";
AjaxResponse_SignOut responseData = new AjaxResponse_SignOut(model);
String messageTitle = Localiser["signin.caption.signout"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
RefreshModel(model);
// Retrieve user account - in case sign-out needs to be recorded.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(SessionData_Online.User.UserAccountId);
if (userAccount == null)
{
SaLogger.Warn(fn + " System error - failed to retrieve account details.");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.system"]));
}
// Clear user session.
CloseUserSession();
SaLogger.Info(fn + " User session closed for " + userAccount.EmailAddress, true);
// Clear 'Keep Logged In' cookie.
ClearKeepLoggedInCookie();
// Force return to home page.
responseData.SetGoHome(); // (messageTitle, Localiser["security.msg.signout"]);
return Json(responseData);
}
//-------------------------------------------------------------------
// Password complexity request.
//-------------------------------------------------------------------
private class AjaxResponse_PasswordScore : AjaxResponse_Base
{
public int Score { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/ScorePassword")]
public ActionResult Ajax_ScorePassword(Model_Security model)
{
// Validate referrer.
if (!ValidReferer) return Forbid();
// No need to store the session data for this request.
StoreSessionData = false;
// Score pasword.
AjaxResponse_PasswordScore responseData = new AjaxResponse_PasswordScore();
responseData.Score = SaxPasswordHelper.GetPasswordStrength(model.Password);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Change Password request.
//-------------------------------------------------------------------
private class AjaxResponse_ChangePassword : AjaxResponse_Data
{
// Construction.
public AjaxResponse_ChangePassword(SaxModel_Online model) : base(model) { }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/StorePassword")]
public ActionResult Ajax_StorePassword(Model_Security model)
{
String fn = GetType().Name + ".Ajax_StorePassword";
AjaxResponse_ChangePassword responseData = new AjaxResponse_ChangePassword(model);
String messageTitle = Localiser["security.caption.change.password"];
// Validate referrer.
if (!ValidReferer) return Forbid();
// If 'Request Password' encrypted params (from email link) are present,
// Session is not required and password can be set directly against the user.
SaxUserAccount userAccount = null;
bool validRequestPasswordParams = false;
bool validSessionRequired = true;
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(model.rpe);
if (requestPasswordParams.IsValid())
{
// Retrieve user account details and validate email address.
userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId);
if (requestPasswordParams.IsValid(userAccount))
{
validRequestPasswordParams = true;
validSessionRequired = false;
}
else
{
SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. Id: " + SysId.ToString(requestPasswordParams.UserAccountId) + ". Email: " + requestPasswordParams.EmailAddress);
userAccount = null;
}
}
// User must have a valid session.
if (validSessionRequired && userAccount == null && !ValidUserSession)
return Json(responseData.SetSessionExpired(messageTitle, Localiser["security.msg.warn.sessionExpired"]));
// Retrieve user account if not previously populated.
if (userAccount == null && SessionData_Online != null)
{
userAccount = AccountMgr.GetUserAccountById(SessionData_Online.User.UserAccountId);
}
if (userAccount == null)
return Json(responseData.SetControlError(nameof(model.Password), "System error - failed to retrieve account details."));
// Validate password data.
model.ValidateChangePasswordData(userAccount, responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Note old password.
String oldPasswordHashed = userAccount.PasswordHashed;
// Store password.
userAccount.PasswordHashed = SaxPasswordHelper.GetPasswordHash(userAccount, model.Password);
userAccount.PasswordChangedAt = DateTime.Now;
// Remove new password from history if present.
for (int passwordHistoryIdx = 0; passwordHistoryIdx < userAccount.PasswordHistory.Count; passwordHistoryIdx++)
{
// Compare history password to new value.
if (SaxPasswordHelper.VerifyHashedPassword(userAccount, model.Password, userAccount.PasswordHistory[passwordHistoryIdx]))
{
// New password found in history - remove it.
userAccount.PasswordHistory.RemoveAt(passwordHistoryIdx);
passwordHistoryIdx--;
}
}
userAccount.PasswordHistory.Remove(oldPasswordHashed);
// Add the old password at the start of the history list and validate history length.
userAccount.PasswordHistory.Add(oldPasswordHashed);
while (userAccount.PasswordHistory.Count > 10)
userAccount.PasswordHistory.RemoveAt(userAccount.PasswordHistory.Count - 1);
if (!AccountMgr.UpdateUserAccount(userAccount, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to change password for " + userAccount.EmailAddress + ": " + AccountMgr.ErrorStateMessage);
return Json(responseData.SetControlError(nameof(model.Password), Localiser["security.msg.change.password.failed"]));
}
SaLogger.Info(fn + " Password changed for " + userAccount.EmailAddress, true);
// Construct response.
responseData.Success = true;
if (validRequestPasswordParams)
responseData.SetPopupMessage(messageTitle, Localiser["security.msg.request.password.saved"]);
return Json(responseData);
}
//-------------------------------------------------------------------
// Request Password request.
//-------------------------------------------------------------------
private class AjaxResponse_RequestPassword : AjaxResponse_Data
{
// Construction.
public AjaxResponse_RequestPassword(SaxModel_Online model) : base(model) { }
/// <summary>Message to be displayed for the user to 'OK' before returning to the Sign-in page.</summary>
public String SuccessMsg { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Security/Ajax/RequestNewPassword")]
public ActionResult Ajax_RequestNewPassword(Model_Security model)
{
String fn = GetType().Name + ".Ajax_RequestNewPassword";
AjaxResponse_RequestPassword responseData = new AjaxResponse_RequestPassword(model);
String messageTitle = Localiser["security.caption.request.password"];
// Validate referrer.
if (!ValidReferer) return Forbid();
// Validate data.
model.ValidateRequestPasswordData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Retrieve user account.
SaxUserAccount userAccount = AccountMgr.GetUserAccountByEmailAddress(model.EmailAddress);
if (userAccount == null)
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account does not exist");
else if (!userAccount.IsStatusActive)
SaLogger.Warn(fn + " Unable to send password request email to " + model.EmailAddress + " - account is not active");
else
{
// Send password request email.
SaEmailData emailData = new SaEmailData();
emailData.Config = EmailConfig;
emailData.AddressesTo.Add(model.EmailAddress);
emailData.Subject = Localiser["security.caption.request.password"];
PopulateRequestPasswordEmailBody(emailData, userAccount);
if (EmailConsole.SendEmail(emailData))
SaLogger.Info(fn + " Password request email sent to " + model.EmailAddress, true);
else SaLogger.Warn(fn + " Failed to send password request email to " + model.EmailAddress);
}
// Construct response.
responseData.Success = true;
responseData.SuccessMsg = Localiser["security.msg.request.password.email.sent"];
return Json(responseData);
}
private void PopulateRequestPasswordEmailBody(SaEmailData emailData, SaxUserAccount userAccount)
{
// Will need to handle culture (language/country) as and when introduced.
emailData.MessageBody = SaxResourceHelper.GetResourceText(SaxResourceHelper.TextResource.Email_Security_RequestPassword_Html);
emailData.HtmlBody = true;
// Replace dynamic tags.
EmailConsole.ReplaceDynamicEmailBodyTags(emailData, null, userAccount);
// Replace dynamic tag for link back to change password page.
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.RequestPassword]", GenerateRequestPasswordLink(this, userAccount.Id, userAccount.EmailAddress, ""));
SaEmailConsole.ReplaceDynamicEmailTag(emailData, "[#Link.ExpiresMinutes]", GetRequestPasswordEmailLinkExpiresMinutes(this).ToString());
}
//-------------------------------------------------------------------
// Password request link.
private static String ParamName_RequestPasswordEncrypted { get; } = "rpe";
private static String ParamName_UserAccountId { get; } = "uai";
private static String ParamName_EmailAddress { get; } = "ema";
private static String ParamName_ExpiresAt { get; } = "exp";
private static String ParamName_Token { get; } = "tok";
// Online Request Password encryption parameters are defined in the base Controller
// so that they can be shared with the Admin application.
internal static String GenerateRequestPasswordLink(SaxController_Online controller, SysId userAccountId, String emailAddress, String requestPasswordToken)
{
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ParamName_UserAccountId, SysId.ToString(userAccountId));
parameters.Add(ParamName_EmailAddress, emailAddress);
parameters.Add(ParamName_ExpiresAt, DateUtil.Format(DateTime.Now.AddMinutes(GetRequestPasswordEmailLinkExpiresMinutes(controller)), DateUtil.Format_XmlDateTime));
parameters.Add(ParamName_Token, requestPasswordToken);
String link =
controller.GetRedirectLocation(RedirectLocation.WebAppBase) + "Security/ChangePassword?" +
ParamName_RequestPasswordEncrypted + "=" + HttpUtility.UrlEncode(EncryptParameters(parameters, Base64Key_RequestPassword_Online, Purpose_RequestPassword_Online));
return link;
}
internal static int GetRequestPasswordEmailLinkExpiresMinutes(SaxController_Online controller)
{
int expiresMinutes = controller.AppConfig.Security.SignIn.PasswordRequest.EmailLinkExpiresMins;
int expiresMinutes_Min = Sax.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Min;
int expiresMinutes_Max = Sax.Config.Security.SignIn.PasswordRequest.EmailLinkExpiresMins_Max;
return Math.Max(expiresMinutes_Min, Math.Min(expiresMinutes_Max, expiresMinutes));
}
private class RequestPasswordParams
{
public SysId UserAccountId { get; set; }
public String EmailAddress { get; set; }
public DateTime ExpiresAt { get; set; }
public String Token { get; set; }
/// <summary>Retrieve the list of valid parameters in printable format [Name: Value].</summary>
public List<String> GetValidParams()
{
List<String> values = new List<String>();
if (SysId.IsValid(UserAccountId))
values.Add("UserAccountId: " + SysId.ToString(UserAccountId));
if (!StringUtil.IsEmpty(EmailAddress))
values.Add("Email: " + EmailAddress);
if (DateUtil.IsValid(ExpiresAt))
values.Add("Expires: " + DateUtil.FormatXmlDateTime(ExpiresAt));
if (!StringUtil.IsEmpty(Token))
values.Add("Token: " + Token);
return values;
}
/// <summary>Verifiy that the account Id and email are populated and the data hasn't expired.</summary>
public bool IsValid()
{
return
SysId.IsValid(UserAccountId) &&
!StringUtil.IsEmpty(EmailAddress) &&
DateUtil.IsValid(ExpiresAt) && ExpiresAt >= DateTime.Now;
}
/// <summary>Verify that the data is valid and the account details match.</summary>
public bool IsValid(SaxUserAccount userAccount)
{
return
IsValid() &&
userAccount != null &&
userAccount.Id.Equals(UserAccountId) &&
userAccount.EmailAddress.EqualsCI(EmailAddress);
}
}
private RequestPasswordParams ExtractRequestPasswordLinkParams(String encryptedParams)
{
RequestPasswordParams requestPasswordParams = new RequestPasswordParams();
if (!StringUtil.IsEmpty(encryptedParams))
{
NameValueCollection linkParams = DecryptParameters(encryptedParams, Base64Key_RequestPassword_Online, Purpose_RequestPassword_Online);
requestPasswordParams.UserAccountId = new SysId(StringUtil.ToInt(linkParams.Get(ParamName_UserAccountId)));
requestPasswordParams.EmailAddress = linkParams.Get(ParamName_EmailAddress);
requestPasswordParams.ExpiresAt = DateUtil.ParseXmlDateTime(linkParams.Get(ParamName_ExpiresAt));
requestPasswordParams.Token = linkParams.Get(ParamName_Token);
}
return requestPasswordParams;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,129 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
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 System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.App;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Security Controller.
//=======================================================================
public partial class SecurityController : SaxController_Online
{
//-------------------------------------------------------------------
// Data members.
//-------------------------------------------------------------------
// Construction.
public SecurityController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
return RedirectToAction("Signin", "Security");
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Signin()
{
// Redirect home if signed-in.
if (ValidUserSession)
return RedirectToAction("", "");
Model_Security model = new Model_Security(Localiser);
return View(model);
}
//-------------------------------------------------------------------
// Security - Change password
/// <summary>
/// Security - Change Password action.
/// <para>- GET from request password email link.</para>
/// <para>- POST with user signed-in. Browser cache prohibited.</para>
/// </summary>
[HttpGet, HttpPost]
public IActionResult ChangePassword(Model_Security model)
{
String fn = GetType().Name + ".ChangePassword";
bool validSessionRequired = true;
if (Request.Method.EqualsCI(HttpMethods.Get))
{
// Check for encrypted parameters from 'Request Password' email link.
// NB Only allowed as a 'GET' request.
String encryptedParams = Request.Query[ParamName_RequestPasswordEncrypted];
RequestPasswordParams requestPasswordParams = ExtractRequestPasswordLinkParams(encryptedParams);
if (requestPasswordParams.IsValid())
{
String logSuffix = requestPasswordParams.GetValidParams().Concat("", " ", ".");
using (SaxUserAccount userAccount = AccountMgr.GetUserAccountById(requestPasswordParams.UserAccountId))
{
if (userAccount.IsStatusActive)
{
if (requestPasswordParams.IsValid(userAccount))
{
model.rpe = encryptedParams;
validSessionRequired = false;
}
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but invalid. " + logSuffix);
}
else SaLogger.Warn(fn + " 'Request Password' parameters supplied but account is not active. " + logSuffix);
}
}
}
// Must have a valid session to change password unless linked from Request Password above.
if (validSessionRequired && !ValidUserSession)
return RedirectToAction("", "");
// Configure page.
model.PageCaption = Localiser["security.caption.change.password"];
return View(model);
}
//-------------------------------------------------------------------
// Security - Request Password
//-------------------------------------------------------------------
/// <summary>
/// Sign-in Request Password action.
/// <para>- User not signed-in.</para>
/// <para>- GET only with browser cache allowed.</para>
/// </summary>
[HttpGet]
public IActionResult RequestPassword()
{
Model_Security model = new Model_Security(Localiser);
// Configure page.
model.PageCaption = Localiser["security.caption.request.password"];
model.ProhibitCache = false;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,371 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Saxonite.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Utils;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Saxonite.Net.Standard.Base.App;
using static Saxonite.Net.Standard.Base.App.Sax.Config;
using static Saxonite.Net.Standard.Base.Bus.SaxArticle;
using Saxonite.Net.Standard.Base.Bus.Support;
using static Saxonite.Net.Standard.Base.App.Sax;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Support Controller - Feedback - Ajax actions.
//=======================================================================
partial class SupportController
{
//-------------------------------------------------------------------
// Data definitions.
private class UserReportResponseListData : AjaxResponse_Data.ItemData_Base
{
// Account Id only populated while account names are being queried, then nulled.
public SysId UserAccountId { get; set; }
public String UserDisplayName { get; set; }
public String ReportTypeDesc { get; set; }
public String ReportTitle { get; set; }
public String RecordedOnDisp { get; set; }
public bool? OwnReport { get; set; }
}
private class UserReportResponseItemData : UserReportResponseListData
{
public String ReportType { get; set; }
public String Report { get; set; }
public String RecordedOnForm { get; set; }
}
private void PopulateUserReportResponseListData(Model_Support model, SaxUserReport userReport, SaxUserAccount userAccount, UserReportResponseListData data)
{
data.Id = model.EncryptId(userReport.Id);
data.UserDisplayName = userAccount.GetDisplayName();
data.ReportTypeDesc = Localiser.GetUserReportTypeCaption(userReport.ReportType);
data.ReportTitle = userReport.Title;
data.RecordedOnDisp = model.FormatDateForDisplay(userReport.RecordedOn);
data.OwnReport = userAccount.Id.Equals(SessionUserAccountId) ? (bool?)true : null;
}
private void PopulateUserReportResponseItemData(Model_Support model, SaxUserReport userReport, SaxUserAccount userAccount, UserReportResponseItemData data)
{
PopulateUserReportResponseListData(model, userReport, userAccount, data);
data.ReportType = UserReportTypeCodes.GetCodeForType(userReport.ReportType);
data.Report = userReport.Report;
data.RecordedOnForm = model.FormatDateForForm(userReport.RecordedOn);
data.CreatedOnFmt = model.FormatDateForDisplay(userReport.CreateStamp.On);
data.StatusCode = userReport.StatusCode;
data.StatusDesc = userReport.StatusDesc;
}
//-------------------------------------------------------------------
// Support - Get User Reports.
//-------------------------------------------------------------------
private class AjaxResponse_UserReportsData : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserReportsData(SaxModel_Online model) : base(model) { }
// Reference data.
public List<ItemCodeDesc> ReportTypes { get; } = new List<ItemCodeDesc>();
// User Report list.
public List<UserReportResponseListData> Reports { get; } = new List<UserReportResponseListData>();
}
// Ajax request.
[HttpPost]
[Route("Support/Ajax/GetUserReports")]
public IActionResult GetUserReports(Model_Support model)
{
String fn = GetType().Name + ".GetUserReports";
AjaxResponse_UserReportsData responseData = new AjaxResponse_UserReportsData(model);
String messageTitle = Localiser["support.caption.user.reports"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Reference data - Report types.
responseData.ReportTypes.Add(new AjaxResponse_Data.ItemCodeDesc());
List<Sax.UserReportType> validReportTypes = new List<Sax.UserReportType>(EnumUtil.GetValues<Sax.UserReportType>());
foreach (TypeCodeDesc reportType in UserReportTypeCodes)
{
if (!validReportTypes.Contains((Sax.UserReportType)reportType.Type)) continue;
responseData.ReportTypes.Add(new AjaxResponse_Data.ItemCodeDesc() { Code = reportType.Code, Desc = Localiser.GetUserReportTypeCaption((Sax.UserReportType)reportType.Type) });
}
// Item filter.
SaxUserReport.Params prmUserReport = new SaxUserReport.Params();
prmUserReport.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserReport.StatusCat = SaxUserReport.StatusCat.NotDeleted;
model.Paging.ItemTotal = (ulong)SupportMgr.GetUserReportCount(prmUserReport);
model.PopulatePagingTo(prmUserReport.ResultsPaging);
// Item columns.
ColumnDfn colId = SupportMgr.GetUserReportColumnDfn(SaxUserReport.Column.Id);
ColumnDfn colUserAccountId = SupportMgr.GetUserReportColumnDfn(SaxUserReport.Column.UserAccountId);
ColumnDfn colReportType = SupportMgr.GetUserReportColumnDfn(SaxUserReport.Column.ReportType);
ColumnDfn colReportTitle = SupportMgr.GetUserReportColumnDfn(SaxUserReport.Column.Title);
ColumnDfn colRecordedOn = SupportMgr.GetUserReportColumnDfn(SaxUserReport.Column.RecordedOn);
ColumnDfn colStatus = SupportMgr.GetUserReportColumnDfn(SaxUserReport.Column.Status);
List<ColumnDfn> columns = new List<ColumnDfn>()
{
colId,
colUserAccountId, colReportType, colReportTitle,colRecordedOn,
colStatus,
};
// Sort columns.
prmUserReport.SortColumns.Add(colRecordedOn);
prmUserReport.SortColumns.Last().SortAscending = false;
prmUserReport.SortColumns.Add(colId);
prmUserReport.SortColumns.Last().SortAscending = false;
// User account details must be queried separately - different database!
HashSet<SysId> userAccountIds = new HashSet<SysId>();
// Query items.
using (DbResultSet resultSet = SupportMgr.ExecuteUserReportQuery(columns, prmUserReport))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId userAccountId = new SysId(values.GetLong(colUserAccountId.DataName));
userAccountIds.Add(userAccountId);
Sax.UserReportType reportType = (Sax.UserReportType)values.GetLong(colReportType.DataName);
UserReportResponseListData userReportData = new UserReportResponseListData()
{
Id = model.EncryptId(new SysId(values.GetLong(colId.DataName))),
UserAccountId = userAccountId,
ReportTypeDesc = Localiser.GetUserReportTypeCaption(reportType),
ReportTitle = values.GetString(colReportTitle.DataName),
RecordedOnDisp = model.FormatDateForDisplay(values.GetDate(colRecordedOn.DataName)),
OwnReport = userAccountId.Equals(SessionUserAccountId) ? (bool?)true : null,
StatusCode = values.GetString(colStatus.DataName),
StatusDesc = SacsSystem.GetStatusDescForCode(values.GetString(colStatus.DataName))
};
responseData.Reports.Add(userReportData);
}
}
if (model.Paging.IsPaged)
model.Paging.ItemCount = (ushort)responseData.Reports.Count;
// Query and populate User Account details.
if (userAccountIds.Any())
{
// User Account columns.
ColumnDfn colUserAccount_Id = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Id);
ColumnDfn colUserAccount_Title = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.Title);
ColumnDfn colUserAccount_FirstName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.FirstName);
ColumnDfn colUserAccount_LastName = AccountMgr.GetUserAccountColumnDfn(SaxUserAccount.Column.LastName);
List<ColumnDfn> userAccountCols = new List<ColumnDfn>() { colUserAccount_Id, colUserAccount_Title, colUserAccount_FirstName, colUserAccount_LastName };
// User Account filter.
SaxUserAccount.Params prmUserAccount = new SaxUserAccount.Params();
prmUserAccount.IdsIncluded.AddRange(userAccountIds);
prmUserAccount.StatusCat = SaxUserAccount.StatusCat.Any;
// Query user accounts and populate properties to report list.
using (DbResultSet resultSet = AccountMgr.ExecuteUserAccountQuery(userAccountCols, prmUserAccount))
{
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
{
NamedValue.Map values = resultSet.GetRecordValues();
SysId userAccountId = new SysId(values.GetLong(colUserAccount_Id.DataName));
String title = values.GetString(colUserAccount_Title.DataName);
String firstName = values.GetString(colUserAccount_FirstName.DataName);
String lastName = values.GetString(colUserAccount_LastName.DataName);
foreach (UserReportResponseListData userReportData in responseData.Reports.FindAll(item => item.UserAccountId.Equals(userAccountId)))
{
userReportData.UserDisplayName = SaxUserAccount.GetDisplayName(title, firstName, lastName);
userReportData.UserAccountId = null;
}
}
}
// Clear any remaining User Account Ids.
foreach (UserReportResponseListData userReportData in responseData.Reports.FindAll(item => item.UserAccountId != null))
userReportData.UserAccountId = null;
}
// Complete response.
responseData.SetPaging(model);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Get User Report ajax request.
//-------------------------------------------------------------------
private class AjaxResponse_UserReport : AjaxResponse_Data
{
// Construction.
public AjaxResponse_UserReport(SaxModel_Online model) : base(model) { }
// Sport details.
public UserReportResponseItemData Report { get; } = new UserReportResponseItemData();
}
//-------------------------------------------------------------------
[HttpPost]
[Route("Support/Ajax/GetUserReport")]
public IActionResult Ajax_GetUserReport(Model_Support model)
{
String fn = GetType().Name + ".Ajax_GetUserReport";
String messageTitle = Localiser["support.caption.user.report"];
AjaxResponse_UserReport responseData = new AjaxResponse_UserReport(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Identify item.
SysId userReportId = model.ExtractSysId(model.UserReport.Id);
if (!SysId.IsValid(userReportId))
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.specified"]));
// Retrieve item.
SaxUserReport userReport = SupportMgr.GetUserReportById(userReportId);
disposableItems.Add(userReport);
if (userReport == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Report for Id: '" + model.UserReport.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Retrieve user.
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userReport.UserAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Account for Id: '" + userReport.UserAccountId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Construct response.
PopulateUserReportResponseItemData(model, userReport, userAccount, responseData.Report);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
// Store User Report ajax request.
//-------------------------------------------------------------------
[HttpPost]
[Route("Support/Ajax/StoreUserReport")]
public IActionResult Ajax_StoreUserReport(Model_Support model)
{
String fn = GetType().Name + ".Ajax_StoreUserReport";
String messageTitle = Localiser["support.caption.user.report"];
AjaxResponse_UserReport responseData = new AjaxResponse_UserReport(model);
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
if (!SessionData_Online.User.Roles.Exists(item => item.Role == Sax.UserRole.SystemReviewer)) return Forbid();
RefreshModel(model);
// Record disposable items as they are created so all can be disposed when needed.
using (DisposableCollection<IDisposable> disposableItems = new DisposableCollection<IDisposable>())
{
// Validate item properties.
model.ValidateUserReportData(responseData);
if (responseData.HasControlErrors())
return Json(responseData);
// Validate User Report Id against 'Exists' flag.
SysId userReportId = model.ExtractSysId(model.UserReport.Id);
bool userReportExists = StringUtil.ToBool(model.UserReport.Exists);
if (userReportExists && !SysId.IsValid(userReportId))
{
SaLogger.Warn(fn + " Id not specified for existing User Report.");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
if (!userReportExists && SysId.IsValid(userReportId))
{
SaLogger.Warn(fn + " Id specified for new User Report: '" + model.UserReport.Id + "'");
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
// Retrieve user.
SysId userAccountId = SessionUserAccountId;
SaxUserAccount userAccount = AccountMgr.GetUserAccountById(userAccountId);
disposableItems.Add(userAccount);
if (userAccount == null)
{
SaLogger.Warn(fn + " Failed to retrieve User Account for Id: '" + userAccountId + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Retrieve existing item.
SaxUserReport userReport = SupportMgr.GetUserReportById(userReportId);
disposableItems.Add(userReport);
if (userReport == null && SysId.IsValid(userReportId))
{
SaLogger.Warn(fn + " Failed to retrieve User Report for Id: '" + model.UserReport.Id + "'");
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Validate User Report properties.
if (userReport != null)
{
if (!userReport.UserAccountId.Equals(userAccountId))
{
SaLogger.Warn(fn + " Inconsistent User Account Id specified for User Report. Expected: " + userReport.UserAccountId + ". Found: " + userAccountId);
return Json(responseData.SetGoHome(messageTitle, Localiser["msg.error.system"]));
}
}
// If User Report doesn't exist, create a new one.
if (userReport == null)
{
userReport = SupportMgr.CreateUserReport(userAccountId);
disposableItems.Add(userReport);
userReport.RecordedOn = DateTime.Today;
}
// Populate item properties.
if (model.UserReport.ReportType != null)
userReport.ReportType = model.GetReportType();
if (model.UserReport.Title != null)
userReport.Title = model.UserReport.Title;
if (model.UserReport.Report != null)
userReport.Report = model.UserReport.Report;
// Store item.
if (!SysId.IsValid(userReport.Id))
{
if (!SupportMgr.InsertUserReport(userReport, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert user report: " + SupportMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!SupportMgr.UpdateUserReport(userReport, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update user report [Id:" + SysId.ToString(userReport.Id) + "]: " + SupportMgr.ErrorStateMessage);
return Json(responseData.SetPopupMessage(messageTitle, Localiser["msg.error.item.update.failed"]));
}
}
// Construct response.
PopulateUserReportResponseItemData(model, userReport, userAccount, responseData.Report);
responseData.Success = true;
}
return Json(responseData);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
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.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
//=======================================================================
// Online Support Controller.
//=======================================================================
public partial class SupportController : SaxController_Online
{
//-------------------------------------------------------------------
// Construction.
public SupportController(SaxLocaliser localiser, ILogger<HomeController> logger) :
base(localiser, logger)
{
}
//-------------------------------------------------------------------
[HttpGet]
public IActionResult Index()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
// No Support index page.
return RedirectToAction("", "");
}
//-------------------------------------------------------------------
[HttpGet,HttpPost]
public IActionResult Feedback()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Support model = new Model_Support(Localiser);
// User must be a member of the 'Reviewers' role.
if (!SessionData_Online.User.SupportMenuAllowed)
return RedirectToAction("", "");
// Applincation index page.
model.ProhibitCache = false;
return View(model);
}
//-------------------------------------------------------------------
[HttpGet, HttpPost]
public IActionResult Versions()
{
// Validate session.
if (!ValidUserSession)
return RedirectToAction("", "");
Model_Support model = new Model_Support(Localiser);
// User must be a member of the 'Reviewers' role.
if (!SessionData_Online.User.SupportMenuAllowed)
return RedirectToAction("", "");
// Applincation index page.
model.ProhibitCache = false;
return View(model);
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,63 @@
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Helpers
{
//=======================================================================
public static class SaxContactMethodHelper_Online
{
//-------------------------------------------------------------------
public static String GetContactMethodHtml(Sax.ContactMethod method, String value, SaxLocaliser localiser)
{
// Qualify any URL value to ensure that it can be used as the href attribute in an anchor tag.
String url = (SaxContactMethodHelper.GetContactMethodType(method) == Sax.ContactMethodType.WebUrl) ? SacsDataValidationHelper.QualifyAbsoluteUrl(value) : value;
switch (method)
{
// Phone numbers.
case Sax.ContactMethod.PhoneMain:
return ConstructImageElement("icon/contact/phone-24.png") + localiser["label.main"] + ": " + value;
case Sax.ContactMethod.PhoneMobile:
return ConstructImageElement("icon/contact/mobile-24.png") + localiser["label.mobile"] + ": " + value;
case Sax.ContactMethod.PhoneHome:
return ConstructImageElement("icon/contact/phone-24.png") + localiser["label.home"] + ": " + value;
case Sax.ContactMethod.PhoneOffice:
return ConstructImageElement("icon/contact/phone-24.png") + localiser["label.office"] + ": " + value;
// Online.
case Sax.ContactMethod.Email:
return "<a href='mailto:" + value + "'>" + ConstructImageElement("action/mail24.png") + value + " </a>";
case Sax.ContactMethod.Website:
return "<a href='" + url + "' target='" + Sax.Config.Links.Targets.Website + "'>" + ConstructImageElement("icon/contact/www-24.png") + value + " </a>";
case Sax.ContactMethod.Wikipedia:
return "<a href='" + url + "' target='" + Sax.Config.Links.Targets.Wikipedia + "'>" + ConstructImageElement("icon/contact/wikipedia-24.png") + value + " </a>";
// Social media.
case Sax.ContactMethod.LinkedIn:
return "<a href='" + url + "' target='" + Sax.Config.Links.Targets.LinkedIn + "'>" + ConstructImageElement("icon/contact/linkedin-24.png") + value + " </a>";
case Sax.ContactMethod.Twitter:
return "<a href='" + url + "' target='" + Sax.Config.Links.Targets.Twitter + "'>" + ConstructImageElement("icon/contact/x-24.png") + value + " </a>";
case Sax.ContactMethod.Facebook:
return "<a href='" + url + "' target='" + Sax.Config.Links.Targets.Facebook + "'>" + ConstructImageElement("icon/contact/facebook-24.png") + value + " </a>";
case Sax.ContactMethod.Instagram:
return "<a href='" + url + "' target='" + Sax.Config.Links.Targets.Instagram + "'>" + ConstructImageElement("icon/contact/instagram-24.png") + value + " </a>";
}
return method.ToString() + ": " + value;
}
//-------------------------------------------------------------------
// Utilities.
public static String ConstructImageElement(String imageFilepath, String alt = "", String title = "")
{
return "<img src='../img/" + imageFilepath + "' alt='" + StringUtil.EmptyOnNull(alt) + "' title='" + StringUtil.EmptyOnNull(title) + "' />";
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,24 @@
using Saxonite.Net.Core.Web.Base.App;
using System;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class ErrorViewModel : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public ErrorViewModel(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,150 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
/// <summary>
/// Address model data properties.
/// </summary>
public class ModelData_Address : ModelData_Online
{
//-------------------------------------------------------------------
// Construction.
public ModelData_Address(SaxModel_Online model, String controlNameSuffix) : base(model)
{
this.ControlNameSuffix = StringUtil.EmptyOnNull(controlNameSuffix) + "Address";
}
//-------------------------------------------------------------------
// Properties.
private String ControlNameSuffix { get; }
// Address properties.
public String Id { get; set; }
public String Line1 { get; set; }
public String Line2 { get; set; }
public String Line3 { get; set; }
public String Town { get; set; }
public String Province { get; set; }
public String RegionIdProvince { get; set; }
public String Country { get; set; }
public String PostalCode { get; set; }
//-------------------------------------------------------------------
// Utilities.
/// <summary>Have any address values been supplied by the form.</summary>
public bool IsSpecified()
{
if (Id != null || Line1 != null || Town != null || Province != null || Country != null || PostalCode != null)
return true;
return false;
}
/// <summary>Have any address values been populated on the form.</summary>
public bool IsPopulated()
{
if (SysId.IsValid(SysId.ToSysId(Id)) || !StringUtil.IsEmpty(Line1) || !StringUtil.IsEmpty(Town) || !StringUtil.IsEmpty(Province) || !StringUtil.IsEmpty(Country) || !StringUtil.IsEmpty(PostalCode))
return true;
return false;
}
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Address.
public bool ValidateAddressData(SaxController_Base.AjaxResponse_Data responseData)
{
// Are any properties populated.
bool populated = IsPopulated();
// Address Line 1 - optional.
if (Line1 != null)
{
responseData.SetControlValue(ControlNameSuffix + "Line1", Line1);
if (StringUtil.GetLength(Line1) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line1))
responseData.SetControlError(ControlNameSuffix + "Line1", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line1)]);
}
// Address Line 2 - optional.
if (Line2 != null)
{
responseData.SetControlValue(ControlNameSuffix + "Line2", Line2);
if (!StringUtil.IsEmpty(Line2))
{
if (StringUtil.GetLength(Line2) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line2))
responseData.SetControlError(ControlNameSuffix + "Line2", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line2)]);
}
}
// Address Line 3 - optional.
if (Line3 != null)
{
responseData.SetControlValue(ControlNameSuffix + "Line3", Line3);
if (!StringUtil.IsEmpty(Line3))
{
if (StringUtil.GetLength(Line3) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line3))
responseData.SetControlError(ControlNameSuffix + "Line3", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line3)]);
}
}
// Address Town - mandatory if any value populated.
if (Town != null)
{
responseData.SetControlValue(ControlNameSuffix + "Town", Town);
if (StringUtil.IsEmpty(Town) && populated)
responseData.SetControlError(ControlNameSuffix + "Town", Model.Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Town) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Town))
responseData.SetControlError(ControlNameSuffix + "Town", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Town)]);
}
// Address Province - optional.
if (Province != null)
{
responseData.SetControlValue(ControlNameSuffix + "Province", Province);
if (!StringUtil.IsEmpty(Province))
{
if (StringUtil.GetLength(Province) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Province))
responseData.SetControlError(ControlNameSuffix + "Province", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Province)]);
}
}
// Address Country - optional.
if (Country != null)
{
responseData.SetControlValue(ControlNameSuffix + "Country", Country);
if (!StringUtil.IsEmpty(Country))
{
if (StringUtil.GetLength(Country) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Country))
responseData.SetControlError(ControlNameSuffix + "Country", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Country)]);
}
}
// Address PostalCode - optional.
if (PostalCode != null)
{
responseData.SetControlValue(ControlNameSuffix + "PostalCode", PostalCode);
if (!StringUtil.IsEmpty(PostalCode))
{
if (StringUtil.GetLength(PostalCode) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_PostalCode))
responseData.SetControlError(ControlNameSuffix + "PostalCode", Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_PostalCode)]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,38 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
/// <summary>
/// Online model data.
/// </summary>
public class ModelData_Online
{
//-------------------------------------------------------------------
// Construction.
public ModelData_Online(SaxModel_Online model)
{
this.Model = model;
}
//-------------------------------------------------------------------
// Properties.
public SaxModel_Online Model { get; }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,142 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.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.Helpers;
using System;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
/// <summary>
/// Organisation model data properties.
/// </summary>
public class ModelData_Organisation : ModelData_Online
{
//-------------------------------------------------------------------
// Construction.
public ModelData_Organisation(SaxModel_Online model, String controlNameSuffix = "Organisation") : base(model)
{
this.ControlNameSuffix = StringUtil.EmptyOnNull(controlNameSuffix);
Address = new ModelData_Address(model, ControlNameSuffix);
}
//-------------------------------------------------------------------
// Properties.
private String ControlNameSuffix { get; }
// Organisation properties.
public String Id { get; set; }
public String Name { get; set; }
public String Type { get; set; }
public Sax.OrganisationType GetOrganisationType() { return (Sax.OrganisationType)Model.OrganisationTypeCodes.GetTypeForCode(Type); }
public String Category { get; set; }
public Sax.OrganisationCategory GetOrganisationCategory() { return (Sax.OrganisationCategory)Model.OrganisationCategoryCodes.GetTypeForCode(Category); }
public String SportId { get; set; }
public String RegionIdPrimary { get; set; }
public String About { get; set; }
public String Facilities { get; set; }
// Contact Methods.
/// <summary>Placeholder field value to indicate the number of Contact Methods in form (even if none are populated).</summary>
public String ContactMethods { get; set; }
public String[] ContactMethod { get; set; }
public String[] ContactValue { get; set; }
/// <summary>Contact methods to be populated during validation.</summary>
public SaxContactData ContactData { get; set; }
// Address.
public ModelData_Address Address { get; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Organisation.
public bool ValidateOrganisationData(SaxController_Base.AjaxResponse_Data responseData)
{
String fn = this.GetType().Name + ".ValidateOrganisationData";
// Name - mandatory.
responseData.SetControlValue(ControlNameSuffix + nameof(Name), Name);
if (StringUtil.IsEmpty(Name))
responseData.SetControlError(ControlNameSuffix + nameof(Name), Model.Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(Name) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_Name))
responseData.SetControlError(ControlNameSuffix + nameof(Name), Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_Name)]);
else if (SysId.IsValid(Model.ExtractSysId(RegionIdPrimary)))
{
// Ensure that the organisation name is unique within the region.
SaxOrganisation.Params prmOrganisation = new SaxOrganisation.Params();
if (SysId.IsValid(Model.ExtractSysId(Id)))
prmOrganisation.IdsExcluded.Add(Model.ExtractSysId(Id));
prmOrganisation.NameFull = Name;
prmOrganisation.RegionIdsIncluded.Add(Model.ExtractSysId(RegionIdPrimary));
prmOrganisation.StatusCat = SaxOrganisation.StatusCat.Any;
if (Model.AccountMgr.GetOrganisationCount(prmOrganisation) > 0)
responseData.SetControlError(ControlNameSuffix + nameof(Name), Model.Localiser["msg.validation.duplicate.value"]);
}
// Organisation Category - mandatory.
if (StringUtil.IsEmpty(Category))
responseData.SetControlError(ControlNameSuffix + nameof(Category), Model.Localiser["msg.validation.mandatory.value"]);
// Organisation Type - mandatory.
if (StringUtil.IsEmpty(Type))
responseData.SetControlError(ControlNameSuffix + nameof(Type), Model.Localiser["msg.validation.mandatory.value"]);
// Sport - mandatory for certain organisation categories.
if (StringUtil.IsEmpty(SportId) && SaxOrganisation.IsSportMandatory(GetOrganisationCategory()))
responseData.SetControlError(ControlNameSuffix + nameof(SportId), Model.Localiser["msg.validation.mandatory.value"]);
else if (!StringUtil.IsEmpty(SportId) && !SysId.IsValid(Model.ExtractSysId(SportId)))
responseData.SetControlError(ControlNameSuffix + nameof(SportId), Model.Localiser["msg.validation.invalid.value"]);
// Primary Region - mandatory.
if (StringUtil.IsEmpty(RegionIdPrimary))
responseData.SetControlError(ControlNameSuffix + nameof(RegionIdPrimary), Model.Localiser["msg.validation.mandatory.value"]);
else if (!SysId.IsValid(Model.ExtractSysId(RegionIdPrimary)))
responseData.SetControlError(ControlNameSuffix + nameof(RegionIdPrimary), Model.Localiser["msg.validation.invalid.value"]);
// About - optional.
responseData.SetControlValue(ControlNameSuffix + nameof(About), About);
if (StringUtil.GetLength(About) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_About))
responseData.SetControlError(ControlNameSuffix + nameof(About), Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_About)]);
// Facilities - optional.
responseData.SetControlValue(ControlNameSuffix + nameof(Facilities), Facilities);
if (StringUtil.GetLength(Facilities) > Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_Facilities))
responseData.SetControlError(ControlNameSuffix + nameof(Facilities), Model.Localiser["msg.validation.maximum.length", Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_Facilities)]);
// Contact Methods.
if (ContactMethods != null)
{
ContactData = new SaxContactData("");
ContactMethod = ContactMethod ?? new String[0];
ContactValue = ContactValue ?? new String[0];
for (int idx = 0; idx < ContactMethod.Length && idx < ContactValue.Length; idx++)
{
String methodValue = ContactMethod[idx];
String value = ContactValue[idx];
if (methodValue == null && value == null) continue;
Sax.ContactMethod method = EnumUtil.ParseEnum(methodValue, Sax.ContactMethod.Unknown);
if (method > 0 && !value.IsEmpty() && SaxContactMethodHelper.IsValidContactMethodValue(method, value))
ContactData.SetContactMethodValue(method, value);
else SaLogger.Warn(fn + " Invalid Contact Method data. Method: " + methodValue + ". Value: " + value);
}
if (ContactData.Methods.Count != ContactMethods.ToInt())
{
SaLogger.Warn(fn + " Inconsistent Contact Methods received. Expected: " + ContactMethods + ". Received: " + ContactData.Methods.Count);
responseData.SetPopupMessage("caption.contact.method", "System error");
}
}
// Address.
Address.ValidateAddressData(responseData);
return !responseData.HasControlErrors() && responseData.PopupMessage == null;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,276 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Saxonite.Net.Standard.Base.App.Sax;
using static Saxonite.Net.Standard.Base.Bus.SaxArticle;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Account : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Account(SaxLocaliser localiser) :
base(localiser)
{
// Model data.
Organisation = new ModelData_Organisation(this);
// Page includes.
AdditionalFiles_CSS.Add("css/sax-account.css");
AdditionalFiles_JS.Add("js/sax-account.js");
AdditionalFiles_JS.Add("js/sax-pro-usr.js");
AdditionalFiles_JS.Add("js/sax-pro-cfg.js");
}
//-------------------------------------------------------------------
// Contact Properties.
public String AccountEmailAddress { get; set; }
public String AccountTitle { get; set; }
public String AccountFirstName { get; set; }
public String AccountLastName { get; set; }
public String AccountAbout { get; set; }
public String AccountGender { get; set; }
public Sax.Gender GetAccountGender()
{
return (Sax.Gender)StringUtil.ToShort(AccountGender);
}
public String AccountDobDay { get; set; }
public String AccountDobMonth { get; set; }
public String AccountDobYear { get; set; }
// Regions.
public String AccountRegionIdCountry { get; set; }
public String AccountRegionCaptionCountry { get; }
public String AccountRegionIdProvince { get; set; }
public String AccountRegionCaptionProvince { get; }
// Image.
public List<IFormFile> AccountContactImage { get; set; }
//-------------------------------------------------------------------
// Sport properties.
public String UserSportId { get; set; }
public String ProfileSportId { get; set; }
public String ProfileSportExists { get; set; }
public String ProfileSportParticipatingCode { get; set; }
public Sax.SportParticipating ProfileSportParticipating
{
get { return (Sax.SportParticipating)SportParticipatingCodes.GetTypeForCode(ProfileSportParticipatingCode); }
set { ProfileSportParticipatingCode = SportParticipatingCodes.GetCodeForType(value); }
}
public String ProfileSportPreparingCode { get; set; }
public Sax.SportPreparing ProfileSportPreparing
{
get { return (Sax.SportPreparing)SportPreparingCodes.GetTypeForCode(ProfileSportPreparingCode); }
set { ProfileSportPreparingCode = SportPreparingCodes.GetCodeForType(value); }
}
public String ProfileSportOfficiatingCode { get; set; }
public Sax.SportOfficiating ProfileSportOfficiating
{
get { return (Sax.SportOfficiating)SportOfficiatingCodes.GetTypeForCode(ProfileSportOfficiatingCode); }
set { ProfileSportOfficiatingCode = SportOfficiatingCodes.GetCodeForType(value); }
}
public String ProfileSportAbout { get; set; }
public class SportsContactsData
{
// Contacts.
/// <summary>Placeholder field value to indicate the number of Contacts in form (even if none are populated).</summary>
public String Count { get; set; }
public String[] ContactType { get; set; }
public String[] UserAccountId { get; set; }
public String[] Title { get; set; }
public String[] FirstName { get; set; }
public String[] LastName { get; set; }
public String[] EmailAddress { get; set; }
public String[] Phone { get; set; }
/// <summary>Sport Contacts to be populated during validation.</summary>
public SaxSportContactsData ContactsData { get; set; }
}
public SportsContactsData Contacts {get; } = new SportsContactsData();
//-------------------------------------------------------------------
// Organisation properties.
public ModelData_Organisation Organisation { get; }
//-------------------------------------------------------------------
// Validate model data.
//-------------------------------------------------------------------
// Validate contact details.
public bool ValidateContactDetails(SaxController_Base.AjaxResponse_Data responseData)
{
//---------------------------------------------------------------
// Title.
if (AccountTitle != null)
{
responseData.SetControlValue(nameof(AccountTitle), AccountTitle);
if (StringUtil.IsEmpty(AccountTitle))
responseData.SetControlError(nameof(AccountTitle), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(AccountTitle) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_Title))
responseData.SetControlError(nameof(AccountTitle), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_Title)]);
}
// First Name.
if (AccountFirstName != null)
{
responseData.SetControlValue(nameof(AccountFirstName), AccountFirstName);
if (StringUtil.IsEmpty(AccountFirstName))
responseData.SetControlError(nameof(AccountFirstName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(AccountFirstName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(nameof(AccountFirstName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)]);
}
// Last Name.
if (AccountLastName != null)
{
responseData.SetControlValue(nameof(AccountLastName), AccountLastName);
if (StringUtil.IsEmpty(AccountLastName))
responseData.SetControlError(nameof(AccountLastName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(AccountLastName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(nameof(AccountLastName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)]);
}
// About.
if (AccountAbout != null)
{
responseData.SetControlValue(nameof(AccountAbout), AccountAbout);
if (!StringUtil.IsEmpty(AccountAbout))
{
if (StringUtil.GetLength(AccountAbout) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_About))
responseData.SetControlError(nameof(AccountAbout), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_About)]);
}
}
//---------------------------------------------------------------
// Date of birth.
if (AccountDobYear != null)
{
responseData.SetControlValue(nameof(AccountDobYear), AccountDobYear);
short dobYear = StringUtil.ToShort(AccountDobYear);
if (!StringUtil.IsNumeric(AccountDobYear) || dobYear > DateTime.Today.Year)
responseData.SetControlError(nameof(AccountDobYear), Localiser["msg.validation.invalid.value"]);
}
if (AccountDobMonth != null)
{
responseData.SetControlValue(nameof(AccountDobMonth), AccountDobMonth);
short dobMonth = StringUtil.ToShort(AccountDobMonth);
if (!StringUtil.IsEmpty(AccountDobMonth) && (!StringUtil.IsNumeric(AccountDobMonth) || dobMonth < 0 || dobMonth > 12))
responseData.SetControlError(nameof(AccountDobMonth), Localiser["msg.validation.invalid.value"]);
}
if (AccountDobDay != null)
{
responseData.SetControlValue(nameof(AccountDobDay), AccountDobDay);
short dobDay = StringUtil.ToShort(AccountDobDay);
if (!StringUtil.IsEmpty(AccountDobDay) && (!StringUtil.IsNumeric(AccountDobDay) || dobDay < 0 || dobDay > 31))
responseData.SetControlError(nameof(AccountDobDay), Localiser["msg.validation.invalid.value"]);
}
//---------------------------------------------------------------
// Country Region.
SysId regionIdCountry = ExtractSysId(AccountRegionIdCountry);
if (AccountRegionIdCountry != null && SysId.IsValid(regionIdCountry))
{
SaxRegion.Data regionCountry = Internals.GetRegion(regionIdCountry);
if (regionCountry == null)
responseData.SetControlError(nameof(AccountRegionIdCountry), Localiser["msg.validation.invalid.value"]);
}
// Province Region.
if (AccountRegionIdProvince != null)
{
SysId regionIdProvince = ExtractSysId(AccountRegionIdProvince);
SaxRegion.Data regionProvince = Internals.GetRegion(regionIdProvince);
if (regionProvince == null)
responseData.SetControlError(nameof(AccountRegionIdProvince), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Validate Sport data.
public bool ValidateProfileSportData(SaxController_Base.AjaxResponse_Data responseData)
{
String fn = GetType().Name + ".ValidateProfileSportData";
// Sport.
if (ProfileSportId != null)
{
SysId profileSportId = ExtractSysId(ProfileSportId);
if (!SysId.IsValid(profileSportId))
responseData.SetControlError(nameof(ProfileSportId), Localiser["msg.validation.mandatory.value"]);
}
// About.
if (ProfileSportAbout != null)
{
responseData.SetControlValue(nameof(ProfileSportAbout), ProfileSportAbout);
if (!StringUtil.IsEmpty(ProfileSportAbout))
{
if (StringUtil.GetLength(ProfileSportAbout) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSport_About))
responseData.SetControlError(nameof(ProfileSportAbout), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSport_About)]);
}
}
// Sport Contacts.
if (Contacts != null)
{
Contacts.ContactsData = new SaxSportContactsData("");
Contacts.ContactType= Contacts.ContactType ?? new String[0];
Contacts.Title= Contacts.Title ?? new String[0];
Contacts.FirstName = Contacts.FirstName ?? new String[0];
Contacts.LastName = Contacts.LastName ?? new String[0];
Contacts.EmailAddress = Contacts.EmailAddress ?? new String[0];
for (int idx = 0; idx < Contacts.ContactType.Length; idx++)
{
String contactTypeValue = Contacts.ContactType[idx];
String title = idx < Contacts.Title.Length ? Contacts.Title[idx] : null;
String firstName = idx < Contacts.FirstName.Length ? Contacts.FirstName[idx] : null;
String lastName = idx < Contacts.LastName.Length ? Contacts.LastName[idx] : null;
String emailAddress = idx < Contacts.EmailAddress.Length ? Contacts.EmailAddress[idx] : null;
String phone = idx < Contacts.Phone.Length ? Contacts.Phone[idx] : null;
if (StringUtil.IsBlank(contactTypeValue) && StringUtil.IsBlank(lastName)) continue;
Sax.SportContactType contactType= (Sax.SportContactType)SportContactTypeCodes.GetTypeForCode(contactTypeValue);
if (contactType > 0)
{
Contacts.ContactsData.SetContactName(contactType, title, firstName, lastName);
Contacts.ContactsData.SetContactEmailAddress(contactType, emailAddress);
Contacts.ContactsData.SetContactPhone(contactType, phone);
}
else SaLogger.Warn(fn + " Invalid Sport Contact data. Type: " + contactTypeValue + ". Last name: " + lastName);
}
if (Contacts.ContactsData.ContactTypes.Count != Contacts.Count.ToInt())
{
SaLogger.Warn(fn + " Inconsistent Contact Methods received. Expected: " + Contacts.Count + ". Received: " + Contacts.ContactsData.ContactTypes.Count);
responseData.SetPopupMessage("caption.contact.method", "System error");
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Validate Organisation data.
public bool ValidateProfileOrganisationData(SaxController_Base.AjaxResponse_Data responseData)
{
Organisation.ValidateOrganisationData(responseData);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,50 @@
using Sarand.Net.Standard.Base.Bus;
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.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Activity : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Activity(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Activity filter.
public class FilterData
{
public String Search { get; set; }
public String OrganisationId { get; set; }
}
public FilterData Filter { get; } = new FilterData();
// Activity identity.
// ToDo Replace Type + Idx (Id) with Idx and cross-reference stored in Session data.
public String ActivityItemType { get; set; }
public String ActivityItemId { get; set; }
public String ActivityReaction { get; set; }
public Sax.ActivityReaction GetActivityReaction()
{
Sax.ActivityReaction reaction = EnumUtil.ParseEnum(ActivityReaction, Sax.ActivityReaction.Unknown);
if (reaction <= 0 && StringUtil.IsNumeric(ActivityReaction))
reaction = (Sax.ActivityReaction)StringUtil.ToShort(ActivityReaction);
return reaction;
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,156 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Article : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Article(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_CSS.Add("css/sax-art.css");
AdditionalFiles_JS.Add("js/sax-art.js");
}
//-------------------------------------------------------------------
// List configuration.
public class FilterData
{
public String OrganisationId { get; set; }
public String OrganisationCaption { get; set; }
public String Article { get; set; }
}
public FilterData Filter { get; } = new FilterData();
/// <summary>
/// User has rights to author articles for at least one organisation.
/// <para>- Output only - not returned with form data.</para>
/// <para>- Only populated for initial page construction.</para>
/// </summary>
public bool UserCanAuthorOrganisationArticles { get; set; }
/// <summary>
/// User has rights to author articles as a journalist.
/// <para>- Output only - not returned with form data.</para>
/// <para>- Only populated for initial page construction.</para>
/// </summary>
public bool UserCanAuthorJournalistArticles { get; set; }
//-------------------------------------------------------------------
// Article properties.
public String ArticleId { get; set; }
public String ArticleExists { get; set; }
// Organisation.
public String ArticleOrganisationId { get; set; }
// Sport.
public String ArticleSportId { get; set; }
public String ArticleSportCaption { get; set; }
// Region.
public String ArticleRegionId { get; set; }
public String ArticleRegionCaption { get; }
// Article Content.
public String ArticleHeadline { get; set; }
public String ArticleSummary { get; set; }
public String ArticleStatusCode { get; set; }
public SaStatus ArticleStatus { get { return SacsSystem.GetStatusForCode(ArticleStatusCode); } }
// Article content.
public List<String> ArticleContentSegmentBodies { get; set; }
public List<String> ArticleContentSegmentItemImageIds { get; set; }
public List<String> ArticleContentSegmentImageIdxs { get; set; }
// Article images.
public List<IFormFile> ArticleImage { get; set; }
// Article publication.
public String ArticlePublished { get; set; }
public bool ArticlePublish { get; set; }
public String ArticlePublishOn { get; set; }
public DateTime GetArticlePublishOn()
{
return ParseDateFromForm(ArticlePublishOn);
}
public String ArticleExpiresAfterDays { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Article.
public bool ValidateArticleData(SaxController_Base.AjaxResponse_Data responseData)
{
// Article headline.
if (ArticleHeadline != null)
{
responseData.SetControlValue(nameof(ArticleHeadline), ArticleHeadline);
if (StringUtil.IsEmpty(ArticleHeadline))
responseData.SetControlError(nameof(ArticleHeadline), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(ArticleHeadline) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Article_Headline))
responseData.SetControlError(nameof(ArticleHeadline), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Article_Headline)]);
}
// Article summary.
if (ArticleSummary != null)
{
responseData.SetControlValue(nameof(ArticleSummary), ArticleSummary);
if (StringUtil.IsEmpty(ArticleSummary))
responseData.SetControlError(nameof(ArticleSummary), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(ArticleSummary) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Article_Summary))
responseData.SetControlError(nameof(ArticleSummary), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Article_Summary)]);
}
// Article sport.
if (ArticleSportId != null)
{
SysId articleSportId = ExtractSysId(ArticleSportId);
if (!SysId.IsValid(articleSportId))
responseData.SetControlError(nameof(ArticleSportId), Localiser["msg.validation.mandatory.value"]);
}
// Article region.
if (ArticleRegionId != null)
{
SysId articleRegionId = ExtractSysId(ArticleRegionId);
if (!SysId.IsValid(articleRegionId))
responseData.SetControlError(nameof(ArticleRegionId), Localiser["msg.validation.mandatory.value"]);
}
// Article publication.
// Only relevant when the article hasn't yet been published and user has selected 'Publish'.
if (!ArticlePublished.ToBool() && ArticlePublish)
{
String controlName = "ArticlePublication";
DateTime publishedOn = GetArticlePublishOn();
if (StringUtil.IsEmpty(ArticlePublishOn))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!DateUtil.IsValid(publishedOn))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
else if (publishedOn.CompareTo(DateUtil.IncrementDate(DateTime.Today, Sax.Config.Articles.PublishMaxDaysAhead)) > 0)
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Article status.
if (ArticleStatusCode != null)
{
if (StringUtil.IsEmpty(ArticleStatusCode))
responseData.SetControlError(nameof(ArticleStatusCode), Localiser["msg.validation.mandatory.value"]);
else if (!SaxArticle.ValidStatuses.Contains(ArticleStatus))
responseData.SetControlError(nameof(ArticleStatusCode), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,28 @@
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_ContactMethod : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_ContactMethod(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Form properties.
public String ContactMethod { get; set; }
public String ContactValue { get; set; }
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,25 @@
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Home : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Home(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_CSS.Add("css/sax-home.css");
AdditionalFiles_JS.Add("js/sax-home.js");
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,101 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Organisation : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Organisation(SaxLocaliser localiser) :
base(localiser)
{
// Model data.
Organisation = new ModelData_Organisation(this);
// Page includes.
//AdditionalFiles_CSS.Add("css/sax-org.css");
AdditionalFiles_JS.Add("js/sax-org.js");
}
//-------------------------------------------------------------------
// List configuration.
public class OrganisationFilterData
{
public String Organisation { get; set; }
public String RegionId { get; set; }
public String SportId { get; set; }
}
public OrganisationFilterData Filter { get; } = new OrganisationFilterData();
//-------------------------------------------------------------------
// Organisation properties.
public ModelData_Organisation Organisation { get; set; }
// User - for organisation request.
public String ContactEmailAddress { get; set; }
public String ContactFirstName { get; set; }
public String ContactLastName { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Organisation.
public bool ValidateOrganisationRequestData(SaxController_Base.AjaxResponse_Data responseData)
{
// User Email Address - mandatory when supplied.
if (ContactEmailAddress != null)
{
responseData.SetControlValue(nameof(ContactEmailAddress), ContactEmailAddress);
if (StringUtil.IsEmpty(ContactEmailAddress))
responseData.SetControlError(nameof(ContactEmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(ContactEmailAddress))
responseData.SetControlError(nameof(ContactEmailAddress), Localiser["msg.validation.invalid.value"]);
else if (StringUtil.GetLength(ContactEmailAddress) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress))
responseData.SetControlError(nameof(ContactEmailAddress), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)]);
else if (AccountMgr.UserAccountExistsByEmailAddress(ContactEmailAddress))
responseData.SetControlError(nameof(ContactEmailAddress), Localiser["msg.validation.duplicate.value"]);
}
// User First Name - mandatory when supplied.
if (ContactFirstName != null)
{
responseData.SetControlValue(nameof(ContactFirstName), ContactFirstName);
if (StringUtil.IsEmpty(ContactFirstName))
responseData.SetControlError(nameof(ContactFirstName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(ContactFirstName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(nameof(ContactFirstName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)]);
}
// User Last Name - mandatory when supplied.
if (ContactLastName != null)
{
responseData.SetControlValue(nameof(ContactLastName), ContactLastName);
if (StringUtil.IsEmpty(ContactLastName))
responseData.SetControlError(nameof(ContactLastName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(ContactLastName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(nameof(ContactLastName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)]);
}
// Organisation properties.
Organisation.ValidateOrganisationData(responseData);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,236 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using NPOI.SS.Formula.Functions;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Organisation_Events : SaxModel_Online
{
//-------------------------------------------------------------------
// Definitions.
public enum PeriodType
{
FromThisWeek,
FromThisMonth,
FromLastMonth,
FromNextMonth,
Past,
}
//-------------------------------------------------------------------
// Construction.
public Model_Organisation_Events(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_CSS.Add("css/sax-org-evt.css");
AdditionalFiles_JS.Add("js/sax-org-evt.js");
}
//-------------------------------------------------------------------
// List configuration.
public class EventFilterData
{
public String OrganisationId { get; set; }
public String OrganisationCaption { get; set; }
public class PeriodData
{
public String Type { get; set; }
public PeriodType PeriodType { get { return EnumUtil.ParseEnum(Type, PeriodType.FromThisWeek); } }
public String FromDate { get; set; }
public String ToDate { get; set; }
}
public PeriodData Period { get; } = new PeriodData();
public String Event { get; set; }
}
public EventFilterData Filter { get; } = new EventFilterData();
//-------------------------------------------------------------------
// Event properties.
public String EventId { get; set; }
public String EventExists { get; set; }
// Event properties.
public String EventOrganisationId { get; set; }
// Sport.
public String EventSportIdPrimary { get; set; }
// Region.
public String EventRegionIdCountry { get; set; }
// Event Content.
public String EventAtDate { get; set; }
public bool EventAtTimeSet { get; set; }
public String EventAtTime { get; set; }
public String EventDuration { get; set; }
public String EventDurationType { get; set; }
public String EventToDate { get; set; }
public bool EventToDateSet { get; set; }
public String EventToTime { get; set; }
public bool EventToTimeSet { get; set; }
public String EventTypeCode { get; set; }
public String EventSummary { get; set; }
public String EventDetails { get; set; }
public String EventStatusCode { get; set; }
public SaStatus EventStatus { get { return SacsSystem.GetStatusForCode(EventStatusCode); } }
//-------------------------------------------------------------------
// Period types.
public IEnumerable<SelectListItem> EventPeriodTypesForDropdown
{
get
{
List<SelectListItem> values = new List<SelectListItem>();
values.Add(new SelectListItem("From this week", PeriodType.FromThisWeek.ToString(), true));
values.Add(new SelectListItem("From this month", PeriodType.FromThisMonth.ToString()));
values.Add(new SelectListItem("From last month", PeriodType.FromLastMonth.ToString()));
values.Add(new SelectListItem("From next month", PeriodType.FromNextMonth.ToString()));
return values;
}
}
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Event.
public bool ValidateEventData(SaxController_Base.AjaxResponse_Data responseData)
{
// Event type.
if (EventTypeCode != null)
{
String controlName = nameof(EventTypeCode);
if (StringUtil.IsEmpty(EventTypeCode))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event At date.
if (EventAtDate != null)
{
String controlName = nameof(EventAtDate);
DateTime eventOn = ParseDateFromForm(EventAtDate);
if (!DateUtil.IsValid(eventOn))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event At Time Set flag.
if (EventAtTimeSet)
{
String controlName = nameof(EventAtTime);
if (StringUtil.IsBlank(EventAtTime))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event At time.
if (EventAtTime != null && EventAtTimeSet)
{
String controlName = nameof(EventAtTime);
DateTime eventAt = DateUtil.ParseExact(EventAtTime, "HH:mm");
if (StringUtil.IsBlank(EventAtTime))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!DateUtil.IsValid(eventAt))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Event duration.
if (EventDuration != null)
{
//String controlName = nameof(EventDuration);
}
// Event duration type.
if (EventDurationType != null)
{
//String controlName = nameof(EventDurationType);
}
// Event To date.
if (EventToDate != null && EventToDateSet)
{
String controlName = nameof(EventToDate);
DateTime eventOn = ParseDateFromForm(EventToDate);
if (!DateUtil.IsValid(eventOn))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event To Time Set flag.
if (EventToTimeSet)
{
String controlName = nameof(EventToTime);
if (StringUtil.IsBlank(EventToTime))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event To time.
if (EventToTime != null && EventToTimeSet)
{
String controlName = nameof(EventToTime);
DateTime eventTo = DateUtil.ParseExact(EventToTime, "HH:mm");
if (StringUtil.IsBlank(EventToTime))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!DateUtil.IsValid(eventTo))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Event summary.
if (EventSummary != null)
{
String controlName = nameof(EventSummary);
responseData.SetControlValue(controlName, EventSummary);
if (StringUtil.IsEmpty(EventSummary))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(EventSummary) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationEvent_Summary))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationEvent_Summary)]);
}
// Event details.
if (EventDetails != null)
{
String controlName = nameof(EventDetails);
responseData.SetControlValue(controlName, EventDetails);
if (StringUtil.GetLength(EventDetails) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationEvent_Details))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationEvent_Details)]);
}
// Event sport.
if (EventSportIdPrimary != null)
{
String controlName = "EventSportPrimary";
SysId articleSportId = ExtractSysId(EventSportIdPrimary);
if (!SysId.IsValid(articleSportId))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event region.
if (EventRegionIdCountry != null)
{
String controlName = "EventRegionCountry";
SysId articleRegionId = ExtractSysId(EventRegionIdCountry);
if (!SysId.IsValid(articleRegionId))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Event status.
if (EventStatusCode != null)
{
String controlName = nameof(EventStatus);
if (StringUtil.IsEmpty(EventStatusCode))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (!SaxOrganisationEvent.ValidStatuses.Contains(EventStatus))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,118 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
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.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Organisation_Members : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Organisation_Members(SaxLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sax-org-mbr.css");
AdditionalFiles_JS.Add("js/sax-org-mbr.js");
}
//-------------------------------------------------------------------
// List configuration.
public class MemberFilterData
{
public String OrganisationId { get; set; }
public String OrganisationCaption { get; set; }
public String Member { get; set; }
public String OrganisationRole { get; set; }
}
public MemberFilterData Filter { get; } = new MemberFilterData();
//-------------------------------------------------------------------
// Member properties.
public String MemberId { get; set; }
public String MemberExists { get; set; }
// Member properties.
public String MemberOrganisationId { get; set; }
public String MemberEmailAddress { get; set; }
public String MemberNameSupplied{ get; set; }
public String MemberTitle { get; set; }
public String MemberFirstName { get; set; }
public String MemberLastName { get; set; }
public String MemberRoleCodeDelim = ",";
public List<String> MemberRoleCodes { get; set; }
public List<String> GetMemberRoleCodes()
{
List<String> codes = new List<string>();
foreach (String code in MemberRoleCodes)
{
if (StringUtil.IsEmpty(code)) continue;
codes.AddRange(code.Split(MemberRoleCodeDelim));
}
return codes;
}
public String MemberAdminComment { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Member.
public bool ValidateMemberData(SaxController_Base.AjaxResponse_Data responseData)
{
// Member Email Address.
if (MemberEmailAddress != null)
{
responseData.SetControlValue(nameof(MemberEmailAddress), MemberEmailAddress);
if (StringUtil.IsEmpty(MemberEmailAddress))
responseData.SetControlError(nameof(MemberEmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(MemberEmailAddress) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress))
responseData.SetControlError(nameof(MemberEmailAddress), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(MemberEmailAddress))
responseData.SetControlError(nameof(MemberEmailAddress), Localiser["msg.validation.invalid.value"]);
}
// Member First Name.
bool memberNameSupplied = StringUtil.ToBool(MemberNameSupplied);
if (MemberFirstName != null && memberNameSupplied)
{
responseData.SetControlValue(nameof(MemberFirstName), MemberFirstName);
if (StringUtil.IsEmpty(MemberFirstName))
responseData.SetControlError(nameof(MemberFirstName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(MemberFirstName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(nameof(MemberFirstName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)]);
}
// Member Last Name.
if (MemberLastName != null && memberNameSupplied)
{
responseData.SetControlValue(nameof(MemberLastName), MemberLastName);
if (StringUtil.IsEmpty(MemberLastName))
responseData.SetControlError(nameof(MemberLastName), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(MemberLastName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(nameof(MemberLastName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)]);
}
// Member Admin Comment.
if (MemberAdminComment != null)
{
responseData.SetControlValue(nameof(MemberAdminComment), MemberAdminComment);
if (!StringUtil.IsEmpty(MemberAdminComment) && StringUtil.GetLength(MemberAdminComment) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserOrganisation_AdminComment))
responseData.SetControlError(nameof(MemberAdminComment), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserOrganisation_AdminComment)]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,119 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Organisation_Opportunities : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Organisation_Opportunities(SaxLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sax-org-opp.css");
AdditionalFiles_JS.Add("js/sax-org-opp.js");
}
//-------------------------------------------------------------------
// List configuration.
public class OpportunityFilterData
{
public String OrganisationId { get; set; }
public String OrganisationCaption { get; set; }
public String Opportunity { get; set; }
}
public OpportunityFilterData Filter { get; } = new OpportunityFilterData();
//-------------------------------------------------------------------
// Opportunity properties.
public String OpportunityId { get; set; }
public String OpportunityExists { get; set; }
// Opportunity properties.
public String OpportunityOrganisationId { get; set; }
// Sport.
public String OpportunitySportId { get; set; }
// Region.
public String OpportunityRegionId { get; set; }
// Opportunity Content.
public String OpportunityTypeCode { get; set; }
public String OpportunityPosition { get; set; }
public String OpportunityOutline { get; set; }
public String OpportunityStatusCode { get; set; }
public SaStatus OpportunityStatus { get { return SacsSystem.GetStatusForCode(OpportunityStatusCode); } }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Opportunity.
public bool ValidateOpportunityData(SaxController_Base.AjaxResponse_Data responseData)
{
// Opportunity type.
if (OpportunityTypeCode != null)
{
if (StringUtil.IsEmpty(OpportunityTypeCode))
responseData.SetControlError(nameof(OpportunityTypeCode), Localiser["msg.validation.mandatory.value"]);
}
// Opportunity position.
if (OpportunityPosition != null)
{
responseData.SetControlValue(nameof(OpportunityPosition), OpportunityPosition);
if (StringUtil.IsEmpty(OpportunityPosition))
responseData.SetControlError(nameof(OpportunityPosition), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(OpportunityPosition) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationOpportunity_Position))
responseData.SetControlError(nameof(OpportunityPosition), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationOpportunity_Position)]);
}
// Opportunity outline.
if (OpportunityOutline != null)
{
responseData.SetControlValue(nameof(OpportunityOutline), OpportunityOutline);
if (StringUtil.IsEmpty(OpportunityOutline))
responseData.SetControlError(nameof(OpportunityOutline), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(OpportunityOutline) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationOpportunity_Outline))
responseData.SetControlError(nameof(OpportunityOutline), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationOpportunity_Outline)]);
}
// Opportunity sport.
if (OpportunitySportId != null)
{
SysId articleSportId = ExtractSysId(OpportunitySportId);
if (!SysId.IsValid(articleSportId))
responseData.SetControlError(nameof(OpportunitySportId), Localiser["msg.validation.mandatory.value"]);
}
// Opportunity region.
if (OpportunityRegionId != null)
{
SysId articleRegionId = ExtractSysId(OpportunityRegionId);
if (!SysId.IsValid(articleRegionId))
responseData.SetControlError(nameof(OpportunityRegionId), Localiser["msg.validation.mandatory.value"]);
}
// Opportunity status.
if (OpportunityStatusCode != null)
{
if (StringUtil.IsEmpty(OpportunityStatusCode))
responseData.SetControlError(nameof(OpportunityStatusCode), Localiser["msg.validation.mandatory.value"]);
else if (!SaxOrganisationOpportunity.ValidStatuses.Contains(OpportunityStatus))
responseData.SetControlError(nameof(OpportunityStatusCode), Localiser["msg.validation.invalid.value"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,26 @@
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Policies : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Policies(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,28 @@
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Reference: SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Reference(SaxLocaliser localiser) :
base(localiser)
{}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,57 @@
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Search_Organisations : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Search_Organisations(SaxLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sax-src-org.css");
AdditionalFiles_JS.Add("js/sax-src-org.js");
}
//-------------------------------------------------------------------
// Page configuration.
//-------------------------------------------------------------------
// Form properties.
public class FilterData
{
public String Organisation { get; set; }
public String Type { get; set; }
public String Category { get; set; }
public String RegionId { get; set; }
public String SportId { get; set; }
}
public FilterData Filter { get; } = new FilterData();
public Sax.OrganisationType FilterOrganisationType { get { return (Sax.OrganisationType)OrganisationTypeCodes.GetTypeForCode(Filter.Type); } }
public Sax.OrganisationCategory FilterOrganisationCategory { get { return (Sax.OrganisationCategory)OrganisationCategoryCodes.GetTypeForCode(Filter.Category); } }
public SysId FilterSportId { get { return ExtractSysId(Filter.SportId); } }
public SysId FilterRegionId { get { return ExtractSysId(Filter.RegionId); } }
public String OrganisationId { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,53 @@
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Search_Users : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Search_Users(SaxLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sax-src-usr.css");
AdditionalFiles_JS.Add("js/sax-src-usr.js");
}
//-------------------------------------------------------------------
// Page configuration.
//-------------------------------------------------------------------
// Form properties.
public class FilterData
{
public String User { get; set; }
public String RegionId { get; set; }
public String SportId { get; set; }
}
public FilterData Filter { get; } = new FilterData();
public SysId FilterSportId { get { return ExtractSysId(Filter.SportId); } }
public SysId FilterRegionId { get { return ExtractSysId(Filter.RegionId); } }
public String UserId { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,113 @@
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Security : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Security(SaxLocaliser localiser) :
base(localiser)
{
//AdditionalFiles_CSS.Add("css/sax-security.css");
AdditionalFiles_JS.Add("js/sax-security.js");
}
//-------------------------------------------------------------------
// Page configuration.
public bool RequestPasswordMode { get { return !StringUtil.IsEmpty(rpe); } }
//-------------------------------------------------------------------
// Form properties.
public String EmailAddress { get; set; }
public String PasswordCurrent { get; set; }
public String Password { get; set; }
public String PasswordConfirm { get; set; }
public bool KeepMeSignedIn { get; set; }
/// <summary>Request Password Encrypted params - to allow Change Password without session or requiring old password.</summary>
public String rpe { get; set; }
//-------------------------------------------------------------------
// Validation.
//-------------------------------------------------------------------
// Sign-in.
public bool ValidateSignInData(SaxController_Base.AjaxResponse_Data responseData)
{
// Email address.
responseData.SetControlValue(nameof(EmailAddress), EmailAddress);
if (StringUtil.IsEmpty(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.invalid.value"]);
// Password.
// NB Do not return the password value in the response.
if (StringUtil.IsEmpty(Password))
responseData.SetControlError(nameof(Password), Localiser["msg.validation.mandatory.value"]);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Change password.
public bool ValidateChangePasswordData(SaxUserAccount userAccount, SaxController_Base.AjaxResponse_Data responseData)
{
// Validate Passwords.
// NB Do not return the password values in the response.
// NB Only validate populated values - any null values are not part of the form for this request.
// - 'Current' password is not shown when changing password from 'Request Password' email link.
if (PasswordCurrent != null && StringUtil.IsEmpty(PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["msg.validation.mandatory.value"]);
if (StringUtil.IsEmpty(Password))
responseData.SetControlError(nameof(Password), Localiser["msg.validation.mandatory.value"]);
if (StringUtil.IsEmpty(PasswordConfirm))
responseData.SetControlError(nameof(PasswordConfirm), Localiser["msg.validation.mandatory.value"]);
if (!StringUtil.IsEmpty(Password) && !StringUtil.IsEmpty(PasswordConfirm))
{
if (!Password.Equals(PasswordConfirm))
responseData.SetControlError(nameof(PasswordConfirm), Localiser["msg.validation.invalid.value"]);
if (!SaxPasswordHelper.IsValidPassword(Password))
responseData.SetControlError(nameof(PasswordConfirm), Localiser.GetText("msg.warn.password.invalid", new object[] { SaxPasswordHelper.Params.MinLength, SaxPasswordHelper.Params.MinLengthNoComplexity }));
}
if (!responseData.HasControlErrors())
{
if (PasswordCurrent != null && !SaxPasswordHelper.VerifyHashedPassword(userAccount, PasswordCurrent))
responseData.SetControlError(nameof(PasswordCurrent), Localiser["security.change.password.current.invalid"]);
if (SaxPasswordHelper.VerifyHashedPassword(userAccount, Password))
responseData.SetControlError(nameof(Password), Localiser["security.change.password.not.changed"]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
// Request Password.
public bool ValidateRequestPasswordData(SaxController_Base.AjaxResponse_Data responseData)
{
// Email address.
responseData.SetControlValue(nameof(EmailAddress), EmailAddress);
if (StringUtil.IsEmpty(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.mandatory.value"]);
else if (!SacsDataValidationHelper.IsValidEmailAddress(EmailAddress))
responseData.SetControlError(nameof(EmailAddress), Localiser["msg.validation.invalid.value"]);
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,78 @@
using Sarand.Net.Standard.Base.Core;
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.Core.Web.Base.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Saxonite.Net.Standard.Base.App.Sax.Config;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_Support : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_Support(SaxLocaliser localiser) :
base(localiser)
{
AdditionalFiles_CSS.Add("css/sax-appl.css");
AdditionalFiles_JS.Add("js/sax-appl.js");
}
//-------------------------------------------------------------------
// User Report proerties.
public class UserReportData
{
public String Id { get; set; }
public String Exists { get; set; }
public String UserAccountId { get; set; }
public String ReportType { get; set; }
public String Title { get; set; }
public String Report { get; set; }
// Filter.
public class UserReportFilterData
{
public String User { get; set; }
public String Report { get; set; }
}
public UserReportFilterData Filter { get; } = new UserReportFilterData();
}
public UserReportData UserReport { get; } = new UserReportData();
public Standard.Base.App.Sax.UserReportType GetReportType() { return (Standard.Base.App.Sax.UserReportType)(UserReportTypeCodes.GetTypeForCode(UserReport.ReportType)); }
//-------------------------------------------------------------------
// Validate User Report data.
public bool ValidateUserReportData(SaxController_Base.AjaxResponse_Data responseData)
{
// Report Type.
String controlName = "UserReportType";
if (UserReport.ReportType != null)
{
if (StringUtil.IsEmpty(UserReport.ReportType))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Report Title.
controlName = "UserReportTitle";
if (UserReport.Title != null)
{
responseData.SetControlValue(controlName, UserReport.Title);
if (StringUtil.IsEmpty(UserReport.Title))
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(UserReport.Title) > GetControlMaxLength(SaxDbDfn_Support.ColumnIdx.UserReport_Title))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Support.ColumnIdx.UserReport_Title)]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,112 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_UserEligibility : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_UserEligibility(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// User properties.
public class UserAccountData
{
public String Id { get; set; }
}
public UserAccountData UserAccount { get; } = new UserAccountData();
// Eligibility properties.
public class EligibilityData
{
/// <summary>User Experience Id.</summary>
public String Id { get; set; }
/// <summary>Flag to indicate whether item is being updated.</summary>
public String Exists { get; set; }
/// <summary>User Experience Region Id.</summary>
public String RegionId { get; set; }
/// <summary>User Experience Type.</summary>
public String Type { get; set; }
/// <summary>User Experience Info.</summary>
public String Info { get; set; }
public Sax.EligibilityType getEligibilityType()
{
if (Type != null)
return (Sax.EligibilityType)SaxSystem.GetCodesOfType(Sax.CodeType.EligibilityType).GetTypeForCode(Type);
return Sax.EligibilityType.Unknown;
}
/// <summary>User Experience Expiry date.</summary>
public String ExpiresOn { get; set; }
}
public EligibilityData Eligibility { get; } = new EligibilityData();
//-------------------------------------------------------------------
// Validate model data.
//-------------------------------------------------------------------
// Validate eligibility details.
public bool ValidateEligibilityDetails(SaxController_Base.AjaxResponse_Data responseData)
{
// Eligibility type.
String controlName = "EligibilityType";
if (Eligibility.Type == null || Eligibility.getEligibilityType() <= 0)
{
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
// Eligibility Region - mandatory for new eligibility.
controlName = "EligibilityRegion";
SysId eligibilityRegionId = ExtractSysId(Eligibility.RegionId);
if ((Eligibility.RegionId == null || !SysId.IsValid(eligibilityRegionId)) && !StringUtil.ToBool(Eligibility.Exists))
{
responseData.SetControlError(controlName, Localiser["msg.validation.mandatory.value"]);
}
else if (Eligibility.RegionId != null)
{
SaxRegion.Data eligibilityRegion = Internals.GetRegion(eligibilityRegionId);
if (eligibilityRegion == null)
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Eligibility info.
controlName = "EligibilityInfo";
if (Eligibility.Info != null)
{
responseData.SetControlValue(controlName, Eligibility.Info);
if (StringUtil.GetLength(Eligibility.Info) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserEligibility_Info))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserEligibility_Info)]);
}
// Eligibility Expiry date.
controlName = "EligibilityExpiry";
if (Eligibility.ExpiresOn != null)
{
if (!StringUtil.IsBlank(Eligibility.ExpiresOn) && !DateUtil.IsValid(ParseDateFromForm(Eligibility.ExpiresOn)))
{
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,102 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_UserSportContact: SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_UserSportContact(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// User Sports Contact properties.
public class SportContactData
{
public String Type { get; set; }
public String EmailAddress { get; set; }
public String Title { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
}
public SportContactData SportContact { get; } = new SportContactData();
//-------------------------------------------------------------------
// Validate User Sport Contact data.
public bool ValidateUserSportContactData(SaxController_Base.AjaxResponse_Data responseData)
{
// Contact Type.
String controlName = "ContactType";
if (SportContact.Type != null)
{
// Contact Type is mandatory.
if (StringUtil.IsBlank(SportContact.Type))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
}
// Email.
controlName = "ContactEmailAddress";
if (SportContact.EmailAddress != null)
{
responseData.SetControlValue(controlName, SportContact.EmailAddress);
// Email is optional.
if (StringUtil.GetLength(SportContact.EmailAddress) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)]);
}
// Title.
controlName = "ContactTitle";
if (SportContact.Title != null)
{
// Title is selected from dropdown so no validation should be required.
if (StringUtil.GetLength(SportContact.Title) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_Title))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_Title)]);
}
// First Name.
controlName = "ContactFirstName";
if (SportContact.FirstName != null)
{
responseData.SetControlValue(controlName, SportContact.FirstName);
// First name is mandatory.
if (StringUtil.IsBlank(SportContact.FirstName))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
else if (StringUtil.GetLength(SportContact.FirstName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)]);
}
// Last Name.
controlName = "ContactLastName";
if (SportContact.LastName != null)
{
responseData.SetControlValue(controlName, SportContact.LastName);
// Last name is mandatory.
if (StringUtil.IsBlank(SportContact.LastName))
responseData.SetControlError(controlName, Localiser["msg.validation.invalid.value"]);
else if (StringUtil.GetLength(SportContact.LastName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName))
responseData.SetControlError(controlName, Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,126 @@
using Microsoft.AspNetCore.Http;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
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.Core.Web.Base.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class Model_UserSportExperience : SaxModel_Online
{
//-------------------------------------------------------------------
// Construction.
public Model_UserSportExperience(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// User Sports Experience Properties.
public String UserSportExperienceId { get; set; }
public String UserSportExperienceExists { get; set; }
public String UserSportId { get; set; }
public String ExperiencePeriodFrom { get; set; }
public String ExperiencePeriodTo { get; set; }
public String ExperienceOrganisationId { get; set; }
public String ExperienceOrganisationName { get; set; }
public String ExperienceSummary { get; set; }
public String ExperienceTeam { get; set; }
public String ExperienceLeague { get; set; }
public String ExperienceSpeciality { get; set; }
public String ExperienceAwards{ get; set; }
// Regions.
public String ExperienceRegionIdNation { get; set; }
public String ExperienceRegionIdProvince { get; set; }
//-------------------------------------------------------------------
// Validate User Sport Experience data.
public bool ValidateProfileSportData(SaxController_Base.AjaxResponse_Data responseData)
{
// Period dates.
DateTime periodFrom = ParseDateFromForm(ExperiencePeriodFrom);
DateTime periodTo = ParseDateFromForm(ExperiencePeriodTo);
// Period From.
if (ExperiencePeriodFrom != null)
{
// Period from date is mandatory.
if (!DateUtil.IsValid(periodFrom))
responseData.SetControlError(nameof(ExperiencePeriodFrom), Localiser["msg.validation.invalid.value"]);
}
// Period To.
if (ExperiencePeriodTo != null)
{
// Period to date is optional must not be before the from date.
if (DateUtil.IsValid(periodTo) && DateUtil.IsValid(periodFrom) && periodTo.CompareTo(periodFrom) < 0)
responseData.SetControlError(nameof(ExperiencePeriodTo), Localiser["msg.validation.invalid.value"]);
}
// Organisation name.
if (ExperienceOrganisationName != null)
{
responseData.SetControlValue(nameof(ExperienceOrganisationName), ExperienceOrganisationName);
if (StringUtil.GetLength(ExperienceOrganisationName) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_OrganisationName))
responseData.SetControlError(nameof(ExperienceOrganisationName), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_OrganisationName)]);
}
// Summary.
if (ExperienceSummary != null)
{
responseData.SetControlValue(nameof(ExperienceSummary), ExperienceSummary);
if (StringUtil.IsEmpty(ExperienceSummary))
responseData.SetControlError(nameof(ExperienceSummary), Localiser["msg.validation.mandatory.value"]);
else if (StringUtil.GetLength(ExperienceSummary) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Summary))
responseData.SetControlError(nameof(ExperienceSummary), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Summary)]);
}
// Team.
if (ExperienceTeam != null)
{
responseData.SetControlValue(nameof(ExperienceTeam), ExperienceTeam);
if (StringUtil.GetLength(ExperienceTeam) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Team))
responseData.SetControlError(nameof(ExperienceTeam), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Team)]);
}
// League.
if (ExperienceLeague != null)
{
responseData.SetControlValue(nameof(ExperienceLeague), ExperienceLeague);
if (StringUtil.GetLength(ExperienceLeague) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_League))
responseData.SetControlError(nameof(ExperienceLeague), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_League)]);
}
// Speciality.
if (ExperienceSpeciality != null)
{
responseData.SetControlValue(nameof(ExperienceSpeciality), ExperienceSpeciality);
if (StringUtil.GetLength(ExperienceSpeciality) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Speciality))
responseData.SetControlError(nameof(ExperienceSpeciality), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Speciality)]);
}
// Awards.
if (ExperienceAwards != null)
{
responseData.SetControlValue(nameof(ExperienceAwards), ExperienceAwards);
if (StringUtil.GetLength(ExperienceAwards) > GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Awards))
responseData.SetControlError(nameof(ExperienceAwards), Localiser["msg.validation.maximum.length", GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Awards)]);
}
return !responseData.HasControlErrors();
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,315 @@
using Sacs.Web.Components;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Base.App;
using Saxonite.Net.Core.Web.Base.Controllers;
using Saxonite.Net.Core.Web.Base.Models;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
namespace Saxonite.Net.Core.Web.Online.Models
{
//=======================================================================
public class SaxModel_Online : SaxModel_Base
{
//-------------------------------------------------------------------
// Data members.
private class UserCanManageOptions
{
public bool? OrganisationMembers { get; set; } = null;
public bool? OrganisationOpportunities { get; set; } = null;
public bool? OrganisationSchedule { get; set; } = null;
public bool? OrganisationArticles { get; set; } = null;
}
private readonly UserCanManageOptions userCanManage = new UserCanManageOptions();
//-------------------------------------------------------------------
// Protected construction.
protected SaxModel_Online(SaxLocaliser localiser) :
base(localiser)
{
}
//-------------------------------------------------------------------
// Properties.
//-------------------------------------------------------------------
// Session.
public SaxSessionData_Online SessionData_Online { get { return SessionData as SaxSessionData_Online; } }
public SysId SessionUserAccountId { get { return SessionData_Online?.User?.UserAccountId ?? null; } }
//-------------------------------------------------------------------
// Menu options.
public bool OrganisationMembersAllowed { get { return UserCanManageOrganisationMembers; } }
public bool ArticlesAllowed { get { return UserIsJournalist || UserCanManageOrganisationArticles; } }
public bool OrganisationOpportunitiesAllowed { get { return UserCanManageOrganisationOpportunities; } }
public bool OrganisationEventsAllowed { get { return UserCanManageOrganisationSchedule; } }
// Support menu is for admin reviewers only.
public bool SupportMenuAllowed { get { return SessionData_Online?.User.SupportMenuAllowed ?? false; } }
//-------------------------------------------------------------------
// Security profile.
public bool UserIsJournalist { get { return SessionData != null && SessionData.User != null && SessionData.User.IsJournalist; } }
public bool UserCanManageOrganisationMembers
{
get
{
if (userCanManage.OrganisationMembers == null && ValidUserSession)
{
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.MemberAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
userCanManage.OrganisationMembers = (AccountMgr.GetUserOrganisationCount(prmUserOrganisation) > 0);
}
return userCanManage.OrganisationMembers == true ? true : false;
}
}
public bool UserCanManageOrganisationOpportunities
{
get
{
if (userCanManage.OrganisationOpportunities == null && ValidUserSession)
{
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.OpportunityAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
userCanManage.OrganisationOpportunities = (AccountMgr.GetUserOrganisationCount(prmUserOrganisation) > 0);
}
return userCanManage.OrganisationOpportunities == true ? true : false;
}
}
public bool UserCanManageOrganisationSchedule
{
get
{
if (userCanManage.OrganisationSchedule == null && ValidUserSession)
{
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.ScheduleAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
userCanManage.OrganisationSchedule = (AccountMgr.GetUserOrganisationCount(prmUserOrganisation) > 0);
}
return userCanManage.OrganisationSchedule == true ? true : false;
}
}
public bool UserCanManageOrganisationArticles
{
get
{
if (userCanManage.OrganisationArticles == null && ValidUserSession)
{
SaxUserOrganisation.Params prmUserOrganisation = new SaxUserOrganisation.Params();
prmUserOrganisation.UserAccountIdsIncluded.Add(SessionUserAccountId);
prmUserOrganisation.OrganisationMembersOnly = true;
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.Admin);
prmUserOrganisation.OrganisationRolesAny.Add(Sax.OrganisationRole.ArticleAdmin);
prmUserOrganisation.StatusCat = SaxUserOrganisation.StatusCat.ActiveOnly;
userCanManage.OrganisationArticles = (AccountMgr.GetUserOrganisationCount(prmUserOrganisation) > 0);
}
return userCanManage.OrganisationArticles == true ? true : false;
}
}
//-------------------------------------------------------------------
// Item Id Obfuscation.
//-------------------------------------------------------------------
public SysId ExtractSysId(String idx)
{
String fn = GetType().Name + ".ExtractSysId";
if (StringUtil.IsEmpty(idx)) return null;
// Numeric values are assumed to be unencrypted Ids.
if (StringUtil.IsNumeric(idx))
return SysId.ToSysId(idx);
try
{
// Attempt to decrypt.
SysId id = DecryptId(idx);
if (SysId.IsValid(id))
return id;
}
catch (Exception exc)
{
SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc));
}
return null;
}
//-------------------------------------------------------------------
// Id encryption.
// To obfuscate Id values that are sent to and from the browser.
public static String Base64Key_IdObfuscation = SacsEncryptionHelper.EncodePlainKey("|SaxOnIte.Onl1n3-iD+0bfuscAtion|", SacsEncryptionHelper.KeyLength);
private const String Purpose_IdObfuscation = "Saxonite.Online.IdObfuscation";
public static String ParamName_IdObfuscation_Id { get; } = "iob";
public String EncryptId(SysId id)
{
if (!SysId.IsValid(id)) return "";
NameValueCollection idObfuscationParams = new NameValueCollection();
idObfuscationParams.Add(ParamName_IdObfuscation_Id, SysId.ToString(id));
return SaxController_Base.EncryptParameters(idObfuscationParams, Base64Key_IdObfuscation, Purpose_IdObfuscation);
}
public SysId DecryptId(String encryptedId)
{
if (StringUtil.IsEmpty(encryptedId))
return new SysId();
if (StringUtil.IsNumeric(encryptedId))
return SysId.ToSysId(encryptedId);
NameValueCollection idObfuscationParams = SaController.DecryptParameters(encryptedId, Base64Key_IdObfuscation, Purpose_IdObfuscation);
return SysId.ToSysId(idObfuscationParams.Get(ParamName_IdObfuscation_Id));
}
//-------------------------------------------------------------------
// Reference Data.
//-------------------------------------------------------------------
// Regions.
/// <summary>Region Data - to be JSON serialised in Ajax responses.</summary>
public class RegionData
{
/// <summary>Encrypted Id</summary>
public String Id { get; set; }
public int RegionType { get; set; }
public String RegionTypeCode { get; set; }
public String RegionTypeDesc { get; set; }
public String Name { get; set; }
public String ParentId { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public List<RegionData> GetRegionDataList_Active()
{
List<RegionData> regions = new List<RegionData>();
foreach (SaxRegion.Data region in DataCacheConsole.GetActiveRegions())
{
RegionData regionData = new RegionData()
{
Id = GetEncryptedRegionId(region.Id),
RegionType = (int)region.RegionType,
RegionTypeCode = region.RegionType.ToString(),
RegionTypeDesc = Localiser.GetRegionTypeCaption(region.RegionType),
Name = GetRegionCaption(region),
ParentId = SysId.ToString(region.RegionIdParent),
StatusCode = region.StatusCode,
StatusDesc = Localiser.GetStatusCaption(region.Status)
};
regions.Add(regionData);
}
return regions;
}
public String GetEncryptedRegionId(SysId regionId)
{
return DataCacheConsole.GetEncryptedRegionId(regionId, EncryptId(regionId));
}
public String GetRegionCaption(SysId regionId)
{
SaxRegion.Data region = DataCacheConsole.GetAllRegions().Find(item => item.Id.Equals(regionId));
return Localiser.GetRegionName(region);
}
public String GetRegionCaption(SaxRegion.Data region)
{
return Localiser.GetRegionName(region);
}
//-------------------------------------------------------------------
// Sports.
/// <summary>Sport Data - to be JSON serialised in Ajax responses.</summary>
public class SportData
{
/// <summary>Encrypted Id</summary>
public String Id { get; set; }
public String SportIdParent { get; set; }
public String Name { get; set; }
public String StatusCode { get; set; }
public String StatusDesc { get; set; }
}
public List<SportData> GetSportDataList_Active()
{
List<SportData> sports = new List<SportData>();
foreach (SaxSport.Data sport in DataCacheConsole.GetActiveSports())
{
SportData sportData = new SportData()
{
Id = GetEncryptedSportId(sport.Id),
SportIdParent = SysId.IsValid(sport.SportIdParent) ? GetEncryptedSportId(sport.SportIdParent) : null,
Name = GetSportCaption(sport),
StatusCode = sport.StatusCode,
StatusDesc = Localiser.GetStatusCaption(sport.Status)
};
sports.Add(sportData);
}
return sports;
}
public List<SaxSport.Data> GetChildSports_Active(SysId sportIdParent)
{
List<SaxSport.Data> sports = new List<SaxSport.Data>();
if (!SysId.IsValid(sportIdParent)) return sports;
List<SaxSport.Data> activeSports = DataCacheConsole.GetActiveSports();
sports.AddRange(activeSports.FindAll(item => sportIdParent.Equals(item.SportIdParent)));
return sports;
}
public String GetEncryptedSportId(SysId sportId)
{
return DataCacheConsole.GetEncryptedSportId(sportId, EncryptId(sportId));
}
// Sport caption.
public String GetSportCaption(SysId sportId)
{
SaxSport.Data sport = DataCacheConsole.GetAllSports().Find(item => item.Id.Equals(sportId));
return GetSportCaption(sport);
}
public String GetSportCaption(SaxSport.Data sport)
{
if (sport == null) return "";
return Localiser.GetSportName(sport);
}
// Speciality type.
public bool SportSpecialityNotRelevant(SysId sportId)
{
SaxSport.Data sport = DataCacheConsole.GetAllSports().Find(item => item.Id.Equals(sportId));
return SportSpecialityNotRelevant(sport);
}
public bool SportSpecialityNotRelevant(SaxSport.Data sport)
{
if (sport == null) return true;
return sport.SpecialityLabel == Sax.SportSpecialityLabel.NotRelevant;
}
// Speciality label.
public String GetSportSpecialityLabel(SysId sportId)
{
SaxSport.Data sport = DataCacheConsole.GetAllSports().Find(item => item.Id.Equals(sportId));
return GetSportSpecialityLabel(sport);
}
public String GetSportSpecialityLabel(SaxSport.Data sport)
{
if (sport == null) return "";
switch (sport.SpecialityLabel)
{
case Sax.SportSpecialityLabel.Positon:
return Localiser["label.position"];
case Sax.SportSpecialityLabel.Discipline:
return Localiser["label.discipline"];
}
return Localiser["label.speciality"];
}
//-------------------------------------------------------------------
}
//=======================================================================
}
@@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43215",
"sslPort": 44364
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
},
"SaxoniteCoreWebOnline": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
}
}
}
}
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
<RootNamespace>Saxonite.Net.Core.Web</RootNamespace>
<AssemblyName>Saxonite.Net.Core.Web</AssemblyName>
<UserSecretsId>7fcfa869-b56b-4283-a7c6-1b36f2e00b52</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="jQuery" Version="3.7.1" />
<PackageReference Include="jQuery.UI.Combined" Version="1.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.13" />
<PackageReference Include="Moment.js" Version="2.30.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Common-VS\Workspace\SacsCoreWeb\SacsCoreWeb.csproj" />
<ProjectReference Include="..\..\..\..\Common-VS\Workspace\SacsStandardBase\SacsStandardBase.csproj" />
<ProjectReference Include="..\..\..\..\Common-VS\Workspace\SacsStandardDbas\SacsStandardDbas.csproj" />
<ProjectReference Include="..\SaxoniteCoreWeb\SaxoniteCoreWeb.csproj" />
<ProjectReference Include="..\SaxoniteStandardBase\SaxoniteStandardBase.csproj" />
<ProjectReference Include="..\SaxoniteStandardDbas\SaxoniteStandardDbas.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Machine.Sarand602.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="appsettings.Machine.Sarand107.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\css\sax-org-evt.css">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\rjs\sax-rjs.css">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Localisation\SaxOnlineProperties.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CustomToolNamespace></CustomToolNamespace>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,26 @@
@model Model_Account
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Profile - Organisations";
}
<partial name="~/Views/Account/Profile-Organisations.cshtml" />
<script type="text/javascript">
sax.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
sax.lbl.remove = '@(Localiser.GetJavascriptText("label.remove"))';
sax.dlg.title.selectRegionCountry = '@Localiser.GetJavascriptText("reference.caption.country.select")';
sax.dlg.title.selectRegionProvince = '@Localiser.GetJavascriptText("reference.caption.province.select")';
sax.dlg.title.imageUpload = '@Localiser.GetJavascriptText("account.caption.image.upload")';
sax.dlg.title.selectRegion = '@Localiser.GetJavascriptText("reference.caption.region.select")';
sax.dlg.title.selectSport = '@Localiser.GetJavascriptText("reference.caption.sport.select")';
sax.msg.uploadFileSelect = '@Localiser.GetJavascriptText("msg.upload.file.select")';
sax.msg.uploadImageSingle = '@Localiser.GetJavascriptText("msg.upload.image.single")';
sax.msg.uploadFileSizeMax = '@Localiser.GetJavascriptText("msg.upload.file.size.max","{0}")';
sax.msg.uploadFileTypeInvalid = '@Localiser.GetJavascriptText("msg.upload.file.type.invalid","{0}")';
sax.msg.uploadingFile = '@Localiser.GetJavascriptText("msg.upload.file.uploading","{0}")';
</script>
<partial name="~/Views/Common/SportSelect.cshtml" />
<partial name="~/Views/Common/RegionSelect.cshtml" />
@@ -0,0 +1,340 @@
@model Model_Account
@inject SaxLocaliser Localiser
<div class="sax-panel-section ProfileContact sax-populating">
<div class="sax-panel-info">
<div class="sax-header">
<div class="row">
<div class="col sax-action sax-edit"><div class="sax-caption">@Localiser["account.caption.profile.contact"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">
<li class="sax-dropdown-item sax-action sax-edit"><div class="sax-label">@(Localiser["label.update"])</div></li>
</ul>
</li>
</ul>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileContact">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col">
<div class="sax-property AccountEmailAddress hidden">
<div class="row">
<div class="col-sm-4 sax-label text-sm-right">@Localiser["label.email.address"]</div>
<div class="col-sm-8 sax-value"></div>
</div>
</div>
<div class="sax-property AccountName hidden">
<div class="row">
<div class="col-sm-4 sax-label text-sm-right">@Localiser["label.name"]</div>
<div class="col-sm-8 sax-value"></div>
</div>
</div>
<div class="sax-property AccountLocation hidden">
<div class="row">
<div class="col-sm-4 sax-label text-sm-right">@Localiser["label.location"]</div>
<div class="col-sm-8 sax-value"></div>
</div>
</div>
</div>
<div class="fixed">
@* Account Contact image *@
<div class=""><img src="" class="sax-contact-image AccountContact" alt="" title="" /></div>
</div>
</div>
</div>
</div>
@* User Contact Details form *@
<div class="sax-panel-form">
@* User Contact Details form - Header *@
<div class="sax-header">
<div class="row">
<div class="col"><div class="sax-caption">@Localiser["account.caption.profile.contact"]</div></div>
<div class="fixed">
</div>
</div>
</div>
@* User Contact Details form - Content *@
<div class="sax-content">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmProfileContact", @class = "sax-form", @onSubmit = "return false;" }))
{
@* User email *@
<div class="sax-form-control AccountEmailAddress">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifAccountEmailAddress"><span>@Localiser["label.email"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextBoxFor(model => model.AccountEmailAddress, new { @id = "ifAccountEmailAddress", @class = "AccountEmailAddress sax-input", @readonly = "readonly", @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* User Name *@
<div class="row">
<div class="col-sm-3">
<div class="sax-label d-none d-sm-block">
<label for="ifAccountFirstName"><span>@Localiser["label.name"]</span></label>
</div>
</div>
<div class="col-sm row">
<div class="col-sm-4">
@* User Title *@
<div class="sax-form-control AccountTitle pr8">
<div class="sax-label">
<label for="cbAccountTitle"><span>@Localiser["label.title"]</span></label>
</div>
<div class="sax-control">
@Html.DropDownListFor(model => model.AccountTitle, Model.TitlesForSelect, Localiser["label.select"], new { @id = "cbAccountTitle", @class = "AccountTitle sax-select" })
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-sm-4">
@* User first name *@
<div class="sax-form-control AccountFirstName pr8">
<div class="sax-label">
<label for="ifAccountFirstName"><span>@Localiser["label.first.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.AccountFirstName, new { @id = "ifAccountFirstName", @class = "AccountFirstName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName) })
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-sm-4">
@* User last name *@
<div class="sax-form-control AccountLastName">
<div class="sax-label">
<label for="ifAccountLastName"><span>@Localiser["label.last.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.AccountLastName, new { @id = "ifAccountLastName", @class = "AccountLastName sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
@* About *@
<div class="sax-form-control AccountAbout">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifAccountAbout"><span>@Localiser["label.bio"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextAreaFor(model => model.AccountAbout, new { @id = "ifAccountAbout", @class = "AccountAbout sax-input", @rows = 4, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_About) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Gender *@
<div class="sax-form-control AccountGender">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="cbAccountGender"><span>@Localiser["label.gender"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<select name="AccountGender" id="cbAccountGender" class="AccountGender sax-select"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Date of birth *@
<div class="row">
<div class="col-sm-3">
<div class="sax-form-control AccountDob">
<div class="sax-label">
<label for="ifAccountRegionIdCountry"><span>@Localiser["label.date.of.birth"]</span></label>
</div>
</div>
</div>
<div class="col-sm">
<div class="row">
@* DOB Year *@
<div class="col-4">
<div class="sax-form-control AccountDobYear pr8">
<div class="sax-label">
<label for="cbAccountDobYear"><span>@Localiser["label.year"]</span></label>
</div>
<div class="sax-control">
<select name="AccountDobYear" id="cbAccountDobYear" class="AccountDobYear sax-select" onchange="sax.refreshDob();"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
@* DOB Month *@
<div class="col-4">
<div class="sax-form-control AccountDobMonth pr8">
<div class="sax-label">
<label for="cbAccountDobMonth"><span>@Localiser["label.month"]</span></label>
</div>
<div class="sax-control">
<select name="AccountDobMonth" id="cbAccountDobMonth" class="AccountDobMonth sax-select" onchange="sax.refreshDob();"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
@* DOB Day *@
<div class="col-4">
<div class="sax-form-control AccountDobDay">
<div class="sax-label">
<label for="cbAccountDobDay"><span>@Localiser["label.day"]</span></label>
</div>
<div class="sax-control">
<select name="AccountDobDay" id="cbAccountDobDay" class="AccountDobDay sax-select" onchange="sax.refreshDob();"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
</div>
@* Country Region *@
<div class="sax-form-control AccountRegionIdCountry">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifAccountRegionIdCountry"><span>@Localiser["label.country"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.AccountRegionIdCountry, new { @id = "ifAccountRegionIdCountry", @class = "AccountRegionIdCountry" })
<div class="col"><input type="text" id="ifAccountRegionCaptionCountry" class="AccountRegionCaptionCountry sax-input" readonly="readonly" onDblClick="sax.selectRegionCountry()" /></div>
<div class="fixed"><button class="SelectRegionCountry sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px" onclick="sax.selectRegionCountry()">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Account image *@
<div class="sax-form-control AccountContactImage">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifAccountImage"><span>@Localiser["label.image"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<div id="pnlContactImageDrop" class="sax-panel-drop row">
<div class="fixed">
<div class=""><img src="" class="sax-contact-image AccountContact" alt="" title="" /></div>
</div>
<div class="col">
@Localiser["label.drag.image.here"]
<div class="DropMessage"></div>
<div class="UploadMessage"></div>
<div class="spacer4"></div>
@Localiser["label.alternatively"]:&nbsp;
<button id="pbSelectContactImage" class="sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px;z-index:999;" onclick="sax.selectContactImage()">@Localiser["label.upload"]</button>
</div>
<div class="fixed">
<img id="" class="ContactImageUploadProgress" src="~/img/animated/progress-basketball-32.gif" style="display:none;margin:8px 4px;" />
</div>
</div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Action buttons *@
<div class="spacer2"></div>
<div class="row">
<div class="col-6 text-left"><button type="button" class="sax-button" value="" onclick="sax.refreshProfileContactInfo();">@Localiser["label.cancel"]</button></div>
<div class="col-6 text-right"><button type="button" class="sax-button" value="" onclick="sax.storeProfileContact();">@Localiser["label.save"]</button></div>
</div>
}
<div class="spacer2"></div>
</div>
<div id="dlgSelectContactImage" class="sax-dialog">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSelectContactImage", @class = "sax-form", @onSubmit = "return false;" }))
{
<div class="spacer4"></div>
<div class="sax-form-control AccountContactImage">
<div class="row">
<div class="fixed"><div style="padding:0 12px;">@Localiser["label.image"]</div></div>
<div class="col"><input type="file" id="ifContactImage" name="" class="fullwidth" style="" /></div>
</div>
<div class="sax-message"></div>
</div>
<div class="spacer2"></div>
<div class="row">
<div class="col"><div class="UploadMessage"></div></div>
<div class="fixed"><img id="" class="ContactImageUploadProgress" src="~/img/animated/progress-basketball-32.gif" style="display:none;margin:8px 4px;" /></div>
</div>
<div class="spacer2"></div>
}
</div>
</div>
</div>
@@ -0,0 +1,179 @@
@model Model_Account
@inject SaxLocaliser Localiser
@{
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-pro-elg.js");
}
<div class="sax-panel-section ProfileEligibility sax-populating">
<div class="sax-panel-info">
<div class="sax-header">
<div class="row">
<div class="col sax-action sax-edit"><div class="sax-caption">@Localiser["caption.my.eligibility"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">
<li class="sax-dropdown-item sax-action sax-add"><div class="sax-label">@(Localiser["label.add"])</div></li>
<li class="sax-dropdown-item sax-action sax-info"><div class="sax-label">@(Localiser["caption.show.info"])</div></li>
</ul>
</li>
</ul>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileEligibilities">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sax-content">
<div class="sax-panel-results">
<div class="sax-panel-infotip hidden"><div class="sax-close"></div><div class="sax-info"></div></div>
<div class="sax-panel-messages"></div>
@* User Eligibility list. *@
<div class="sax-panel-item-list"></div>
</div>
</div>
@* Item templates *@
<div class="sax-panel-item-template">
@* User Eligibility template *@
<div class="sax-panel-item UserEligibility">
<div class="sax-header row">
<div class="col"><div class="sax-caption"><div class="sax-action EligibilityRegionName"></div></div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu"></ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-6"><div class="EligibilityType sax-note"></div></div>
<div class="col-sm-6 text-sm-right"><div class="EligibilityExpiry sax-note">@Localiser["label.expires"]:&nbsp;<span class="EligibilityExpiresOn"></span></div></div>
</div>
</div>
</div>
@* User Eligibility Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu UserEligibility">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
@* User Eligibility Id form. *@
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmUserEligibilityId", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.Hidden("Eligibility.Id", "", new { @id = "", @class = "UserEligibilityId sax-hidden" })
}
</div>
@* User Eligibility dialog *@
<div id="dlgUserEligibility" class="sax-dialog" title="@Localiser["account.caption.profile.eligibility"]">
<div class="spacer2"></div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmUserEligibility", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" class="UserEligibilityId sax-hidden" name="Eligibility.Id" />
<input type="hidden" class="EligibilityExists sax-hidden" name="Eligibility.Exists" />
@* Region *@
<div class="sax-form-control EligibilityRegion">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifEligibilityRegionId"><span>@Localiser["label.region"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.Hidden("Eligibility.RegionId", "", new { @id = "ifEligibilityRegionId", @class = "EligibilityRegionId" })
<div class="col"><input type="text" id="ifEligibilityRegionName" class="EligibilityRegionName sax-input" readonly="readonly" /></div>
<div class="fixed"><button class="SelectEligibilityRegion sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Eligibility Type *@
<div class="sax-form-control EligibilityType">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifEligibilityType"><span>@Localiser["label.eligibility"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.DropDownList("Eligibility.Type", Model.EligibilityTypesForSelect, "", new { @id = "ifEligibilityType", @class = "EligibilityType sax-select", @readonly = "readonly", @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Info *@
<div class="sax-form-control EligibilityInfo">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifEligibilityInfo"><span>@Localiser["label.info"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextArea("Eligibility.Info", "", new { @id = "ifEligibilityInfo", @class = "EligibilityInfo sax-textarea", @rows = 4, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserEligibility_Info) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Expiry *@
<div class="sax-form-control EligibilityExpiry">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifArticleRegionId"><span>@Localiser["label.expiry"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<div class="row EligibilityExpires">
<div class="fixed"><div class="pl16 pt4 pr8"><input type="checkbox" id="chkEligibilityExpires" class="EligibilityExpires sax-checkbox" />&nbsp;<span class="" onclick="$('#chkEligibilityExpires').click()">@Localiser["label.expires.on"]</span></div></div>
<div class="col">@Html.TextBox("Eligibility.ExpiresOn", "", new { @id = "ifEligibilityExpiresOn", @class = "sax-input sax-calendar EligibilityExpiresOn", @readonly = "readonly" })</div>
</div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
</div>
<script type="text/javascript">
sax.msg.eligibilityInfo = '@Localiser.GetJavascriptText("info.profile.eligibility")';
sax.dlg.title.addUserEligibility = '@Localiser.GetJavascriptText("account.caption.profile.eligibility.add")';
sax.dlg.title.updateUserEligibility = '@Localiser.GetJavascriptText("account.caption.profile.eligibility.update")';
sax.dlg.title.removeUserEligibility = '@Localiser.GetJavascriptText("account.caption.profile.eligibility.remove")';
sax.msg.removeUserEligibility = '@(Localiser.GetJavascriptText("msg.confirm.item.remove"))';
</script>
@@ -0,0 +1,152 @@
@model Model_Account
@inject SaxLocaliser Localiser
@{
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-pro-org.js");
}
@* Profile Organisations *@
<div class="sax-panel-section ProfileOrganisations sax-populating">
<div class="sax-panel-info">
@* Panel caption and burger menu *@
<div class="sax-header">
<div class="row">
<div class="col"><div class="sax-caption">@Localiser["account.caption.profile.organisations"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">
<li class="sax-action sax-users sax-dropdown-item" onclick="sax.orgReq.fn.requestOrganisation();"><div class="sax-label">@Localiser["organisation.caption.organisation.request"]</div></li>
</ul>
</li>
</ul>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Filter Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileOrganisations">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sax-content">
@* Profile Organisations list *@
<div class="sax-panel-item-list sax-panel-results Organisations"></div>
</div>
@* Item templates *@
<div class="sax-panel-item-template">
@* Organisation template *@
<div class="sax-panel-item Organisation">
<div class="row">
<div class="col">
<div class="sax-header row">
<div class="col"><div class="sax-caption"><div class="sax-action OrganisationName"></div></div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu"></ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col">
<div class="row">
<div class="col-sm-6"><div class="OrganisationSport sax-note"></div></div>
<div class="col-sm-6 text-sm-right"><div class="OrganisationRegionPrimary sax-note"></div></div>
</div>
<div class="row">
<div class="col-sm-6"><div class="OrganisationMemberCount sax-note sa-action">@Localiser["label.members"]:&nbsp;<span class="MemberCount"></span></div></div>
<div class="col-sm-6 text-sm-right"><div class="OrganisationOpportunityCount sax-note sa-action">@Localiser["label.opportunities"]:&nbsp;<span class="OpportunityCount"></span></div></div>
</div>
</div>
<div class="fixed"><img src="" class="OrganisationLogo" alt="" title="" style="margin:0 0 0 6px;"></div>
</div>
</div>
</div>
</div>
</div>
@* Organisation Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileOrganisation">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
@* Profile Organisation Id form *@
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmProfileOrganisationId", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Organisation.Id, new { @id = "", @class = "OrganisationId" })
}
</div>
@* Profile Organisation dialog *@
<div id="dlgProfileOrganisation" class="sax-dialog" title="@Localiser["label.organisation"]">
<div class="spacer2"></div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmProfileOrganisation", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.Organisation.Id, new { @id = "", @class = "OrganisationId" })
@Html.HiddenFor(model => model.Organisation.Address.Id, new { @id = "", @class = "OrganisationAddressId" })
@* Core controls - name, category, sport, region *@
<partial name="~/Views/Common/OrganisationControls-Core.cshtml" />
@* Organisation About *@
<div class="sax-form-control OrganisationAbout">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifOrganisationAbout"><span>@Localiser["label.about"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextAreaFor(model => model.Organisation.About, new { @id = "ifOrganisationAbout", @class = "OrganisationAbout sax-input", @rows = 6, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_About) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation Facilities *@
<div class="sax-form-control OrganisationFacilities">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifOrganisationFacilities"><span>@Localiser["label.facilities"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextAreaFor(model => model.Organisation.Facilities, new { @id = "ifOrganisationFacilities", @class = "OrganisationFacilities sax-input", @rows = 6, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_Facilities) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Contact controls *@
<partial name="~/Views/Common/OrganisationControls-Contact.cshtml" />
@* ModelData_Address controls *@
<partial name="~/Views/Common/OrganisationControls-Address.cshtml" />
}
<div class="spacer"></div>
</div>
<partial name="~/Views/Common/OrganisationRequest.cshtml" />
@@ -0,0 +1,260 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Sport Experience panel - list experience for sport */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-spt-exp.js");
}
<div class="sax-panel sax-panel-info ProfileSportExperience hidden">
<script type="text/javascript">
sax.lbl.speciality = '@Localiser.GetJavascriptText("label.speciality")';
</script>
<div class="sax-header">
<div class="row">
<div class="col"><div class="sax-caption"><span>@Localiser["account.caption.profile.sports.experience"] - </span><span class="SportName"></span></div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">
<li class="sax-dropdown-item sax-action sax-add" onclick="sax.addSportExperience()"><div class="sax-label">@(Localiser["label.add"])</div></li>
<li class="sax-dropdown-item sax-action sax-close" onclick="sax.closeProfileSportExperience()"><div class="sax-label">@(Localiser["label.close"])</div></li>
</ul>
</li>
</ul>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Filter Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileSports">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sax-content" style="max-height:none;">
<div class="sax-panel-results ProfileSportExperiences" style="min-height:400px;">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
<div class="sax-panel-item-template">
<div class="sax-panel-item SportExperience">
<div class="sax-header row">
<div class="col">
<div class="sax-caption"><div class="sax-action ExperiencePeriod"></div></div>
</div>
<div class="fixed sax-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu"></ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-12"><div class="ExperienceSummary"></div></div>
</div>
<div class="row">
<div class="col-sm-4"><div class="sax-note"><span class="ExperienceTeam"></span></div></div>
<div class="col-sm-4 justify-content-sm-center"><div class="sax-note"><span class="ExperienceLeague"></span></div></div>
<div class="col-sm-4 justify-content-sm-end"><div class="ax-note"><span class="ExperienceSpeciality"></span></div></div>
</div>
</div>
</div>
@* Filter Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu SportExperience">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
@* Action buttons *@
<div class="spacer2"></div>
<div class="row">
<div class="col text-right pr12"><button type="button" class="sax-button" value="" onclick="sax.closeProfileSportExperience();">@Localiser["label.close"]</button></div>
</div>
<div class="spacer2"></div>
</div>
</div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmSportExperienceId", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" name="UserSportExperienceId" class="UserSportExperienceId" />
}
<div id="dlgSportExperience" class="sax-dialog">
<div class="spacer2"></div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmSportExperience", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" class="UserSportId" name="UserSportId" />
<input type="hidden" class="UserSportExperienceId" name="UserSportExperienceId" />
<input type="hidden" class="UserSportExperienceExists" name="UserSportExperienceExists" />
@* Period *@
<div class="row">
<div class="col-sm-3">
<div class="sax-label pt-sm-4">
<label for=""><span>@Localiser["label.period"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="row">
<div class="col-6">
@* Period From *@
<div class="sax-form-control ExperiencePeriodFrom">
<div class="sax-label">
<label for=""><span>@Localiser["label.from"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" name="ExperiencePeriodFrom" class="ExperiencePeriodFrom sax-input sax-calendar" readonly="readonly" />
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-6">
@* Period To *@
<div class="sax-form-control ExperiencePeriodTo">
<div class="sax-label">
<label for=""><span>@Localiser["label.to"]</span></label>
</div>
<div class="sax-control">
<input type="text" name="ExperiencePeriodTo" class="ExperiencePeriodTo sax-input sax-calendar" readonly="readonly" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
</div>
@* Summary *@
<div class="sax-form-control ExperienceSummary">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.summary"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sax-control">
<textarea name="ExperienceSummary" class="ExperienceSummary sax-textarea" rows="4" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Summary)"></textarea>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation *@
<div class="sax-form-control ExperienceOrganisation">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.club"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sax-control">
<input type="text" name="ExperienceOrganisationName" class="ExperienceOrganisationName sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_OrganisationName)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Speciality *@
<div class="sax-form-control ExperienceSpeciality">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span class="sax-label-text">@Localiser["label.speciality"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sax-control">
<input type="text" name="ExperienceSpeciality" class="ExperienceSpeciality sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Speciality)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Team *@
<div class="sax-form-control ExperienceTeam">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.team"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sax-control">
<input type="text" name="ExperienceTeam" class="ExperienceTeam sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Team)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* League *@
<div class="sax-form-control ExperienceLeague">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.league"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sax-control">
<input type="text" name="ExperienceLeague" class="ExperienceLeague sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_League)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Awards *@
<div class="sax-form-control ExperienceAwards">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.awards"]</span></label>
</div>
</div>
<div class="col-sm-9">
<div class="sax-control">
<textarea name="ExperienceAwards" class="ExperienceAwards sax-textarea" rows="4" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSportExperience_Awards)"></textarea>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
<script type="text/javascript">
sax.lbl.sportExperience = '@Localiser.GetJavascriptText("account.caption.profile.sports.experience")';
</script>
@@ -0,0 +1,191 @@
@model Model_Account
@inject SaxLocaliser Localiser
@{
/* Online Profile Sports. */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-pro-spt.js");
}
<div class="sax-panel-section ProfileSports sax-populating">
<div class="sax-panel-info">
<div class="sax-header">
<div class="row">
<div class="col"><div class="sax-caption">@Localiser["account.caption.profile.sports"]</div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">
<li class="sax-dropdown-item sax-action sax-add"><div class="sax-label">@(Localiser["label.add"])</div></li>
</ul>
</li>
</ul>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Filter Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileSports">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
<div class="sax-content">
<div class="sax-panel-results ProfileSports"></div>
</div>
@* Item templates *@
<div class="sax-panel-item-template">
@* Sport template *@
<div class="sax-panel-item ProfileSport">
<div class="sax-header row">
<div class="col"><div class="sax-action sax-edit SportName"></div></div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">
<li class="sax-dropdown-item sax-action sax-edit"><div class="sax-label">@(Localiser["label.update"])</div></li>
<li class="sax-dropdown-item sax-action sax-experience"><div class="sax-label">@(Localiser["label.experience"])</div></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-4 SportParticipating sax-note">@Localiser["label.participating"]:&nbsp;<span class="SportParticipatingValue"></span></div>
<div class="col-sm-4 SportPreparing sax-note">@Localiser["label.preparing"]:&nbsp;<span class="SportPreparingValue"></span></div>
<div class="col-sm-4 SportOfficiating sax-note">@Localiser["label.officiating"]:&nbsp;<span class="SportOfficiatingValue"></span></div>
</div>
</div>
</div>
@* Sport Burger dropdown template - ul is ignored *@
<ul class="sax-dropdown-menu ProfileSport">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmProfileSportId", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.UserSportId, new { @id = "", @class = "UserSportId" })
}
</div>
<div id="dlgProfileSport" class="sax-dialog">
<div class="spacer"></div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmProfileSport", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" class="UserSportId" name="UserSportId" />
<input type="hidden" class="ProfileSportExists" name="ProfileSportExists" />
@* Sport *@
<div class="sax-form-control ProfileSportId">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifProfileSport"><span>@Localiser["label.sport"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<input type="hidden" id="ifProfileSportId" class="ProfileSportId" name="ProfileSportId" />
<div class="col"><input type="text" id="ifProfileSport" class="ProfileSport sax-input" name="ProfileSport" readonly="readonly" maxlength=128 /></div>
<div class="fixed"><button class="SelectSport sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* About *@
<div class="sax-form-control ProfileSportAbout">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifProfileSportAbout"><span>@Localiser["label.about"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextAreaFor(model => model.ProfileSportAbout, new { @id = "ifProfileSportAbout", @class = "ProfileSportAbout sax-textarea", @rows = 4, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserSport_About) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Contact controls *@
<partial name="~/Views/Common/SportControls-Contacts.cshtml" />
@* Sport Participating *@
<div class="sax-form-control ProfileSportParticipating">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="cbProfileSportParticipating"><span>@Localiser["label.participating"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.DropDownListFor(model => model.ProfileSportParticipatingCode, Model.SportParticipatingCodesForSelect, "", new { @id = "cbProfileSportParticipating", @class = "ProfileSportParticipating sax-select" })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Sport Preparing *@
<div class="sax-form-control ProfileSportPreparing">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="cbProfileSportPreparing"><span>@Localiser["label.preparing"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.DropDownListFor(model => model.ProfileSportPreparingCode, Model.SportPreparingCodesForSelect, "", new { @id = "cbProfileSportPreparing", @class = "ProfileSportPreparing sax-select" })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Sport Officiating *@
<div class="sax-form-control ProfileSportOfficiating">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="cbProfileSportOfficiating"><span>@Localiser["label.officiating"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.DropDownListFor(model => model.ProfileSportOfficiatingCode, Model.SportOfficiatingCodesForSelect, "", new { @id = "cbProfileSportOfficiating", @class = "ProfileSportOfficiating sax-select" })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
}
<div class="spacer"></div>
</div>
<script type="text/javascript">
sax.dlg.title.addUserSport = '@Localiser.GetJavascriptText("account.caption.profile.sport.add")';
sax.dlg.title.updateUserSport = '@Localiser.GetJavascriptText("account.caption.profile.sport.update")';
</script>
@@ -0,0 +1,48 @@
@model Model_Account
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Profile";
}
<div class="sax-profile">
<div class="sax-header">
<div class="sax-caption">@Localiser["account.caption.profile"]</div>
</div>
<div class="sax-content">
<partial name="~/Views/Account/Profile-Contact.cshtml" />
<partial name="~/Views/Account/Profile-Eligibility.cshtml" />
<!--
<partial name="~/Views/Account/Profile-Sports.cshtml" />
<partial name="~/Views/Account/Profile-SportExperiences.cshtml" />
<partial name="~/Views/Account/Profile-Organisations.cshtml" />
-->
</div>
<script type="text/javascript">
sax.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
sax.lbl.remove = '@(Localiser.GetJavascriptText("label.remove"))';
sax.dlg.title.selectRegionCountry = '@Localiser.GetJavascriptText("reference.caption.country.select")';
sax.dlg.title.selectRegionProvince = '@Localiser.GetJavascriptText("reference.caption.province.select")';
sax.dlg.title.imageUpload = '@Localiser.GetJavascriptText("account.caption.image.upload")';
sax.dlg.title.selectRegion = '@Localiser.GetJavascriptText("reference.caption.region.select")';
sax.dlg.title.selectSport = '@Localiser.GetJavascriptText("reference.caption.sport.select")';
sax.msg.uploadFileSelect = '@Localiser.GetJavascriptText("msg.upload.file.select")';
sax.msg.uploadImageSingle = '@Localiser.GetJavascriptText("msg.upload.image.single")';
sax.msg.uploadFileSizeMax = '@Localiser.GetJavascriptText("msg.upload.file.size.max","{0}")';
sax.msg.uploadFileTypeInvalid = '@Localiser.GetJavascriptText("msg.upload.file.type.invalid","{0}")';
sax.msg.uploadingFile = '@Localiser.GetJavascriptText("msg.upload.file.uploading","{0}")';
</script>
<partial name="~/Views/Common/RegionSelect.cshtml" />
<partial name="~/Views/Common/SportSelect.cshtml" />
</div>
@@ -0,0 +1,13 @@
@model Model_Account
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Profile - Sports";
}
<partial name="~/Views/Account/Profile-Sports.cshtml" />
<partial name="~/Views/Common/Sport-Contacts.cshtml" />
<partial name="~/Views/Account/Profile-SportExperiences.cshtml" />
<partial name="~/Views/Common/SportSelect.cshtml" />
@@ -0,0 +1,118 @@
@model Model_Article
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Journalist - Articles";
}
<div class="sax-panel Articles">
<script type="text/javascript">
sax.articlePublish = { maxDaysBefore: '@(Sax.Config.Articles.PublishMaxDaysBefore)', maxDaysAhead: '@(Sax.Config.Articles.PublishMaxDaysAhead)' };
sax.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
sax.lbl.remove = '@(Localiser.GetJavascriptText("label.remove"))';
</script>
@* Filter Form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmArticleFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
@* Filter panel *@
<div class="sax-panel-filter">
<div class="sax-header row">
<div class="col"><div class="sax-caption">@Localiser["article.caption.articles"]</div></div>
<div class="fixed row">
<div class="col">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="sax-content">
@if (Model.UserCanAuthorOrganisationArticles)
{
<div class="sax-control row OrganisationFilter">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.organisation"]</div></div>
<div class="col-sm-8 row">
@Html.HiddenFor(model => model.Filter.OrganisationId, new { @id = "ifFilterOrganisationId", @class = "FilterOrganisationId" })
<div class="col"><input type="text" id="ifFilterOrganisation" class="FilterOrganisation sax-input" readonly="readonly" value="@(Model.Filter.OrganisationCaption)" /></div>
<div class="fixed"><button class="sax-button">@Localiser["label.select"]</button></div>
</div>
</div>
}
<div class="row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm-8"><input type="search" name="Filter.Article" id="ifArticleFilter" class="ArticleFilter sax-input" maxlength="64" /></div>
</div>
</div>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Articles">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
}
@* Results Panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
@* Element templates *@
<div class="sax-panel-item-template">
@* Article item template *@
<div class="sax-panel-item Article">
<div class="sax-header row">
<div class="col">
<div class="sax-caption"><div class="sax-action ArticleHeadline"></div></div>
</div>
<div class="fixed"><div class="OrganisationName pt4 pr16"></div></div>
<div class="fixed"><div class="ArticleFeedback pt4 pr16"></div></div>
<div class="fixed sax-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-12"><div class="ArticleSummary"></div></div>
<div class="col-6"><div class="ArticleSportCaption sax-note"></div></div>
<div class="col-6 right"><div class="ArticleRegionCaption sax-note"></div></div>
</div>
<div class="row">
<div class="col-sm-4"><div class="sax-note">@Localiser["label.created"]:&nbsp;<span class="ArticleCreatedOn"></span></div></div>
<div class="col-sm-4"><div class="ArticlePublished sax-note">@Localiser["label.published"]:&nbsp;<span class="ArticlePublishedOn"></span></div></div>
<div class="col-sm-4 right"><div class="ArticleStatusDesc sax-note"></div></div>
</div>
</div>
</div>
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Article">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
<partial name="~/Views/Common/OrganisationSelect.cshtml" />
<partial name="Panel-ArticleDialog.cshtml" />
@@ -0,0 +1,219 @@
@model Model_Article
@inject SaxLocaliser Localiser
@{
/* Articles - Article dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createArticle = '@Localiser.GetJavascriptText("article.caption.article.create")';
sax.dlg.title.updateArticle = '@Localiser.GetJavascriptText("article.caption.article.update")';
sax.dlg.title.selectRegion = '@Localiser.GetJavascriptText("reference.caption.region.select")';
sax.dlg.title.selectSport = '@Localiser.GetJavascriptText("reference.caption.sport.select")';
</script>
@* Article Id form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmArticleId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.ArticleId, new { @class = "ArticleId" })
}
@* Article dialog and form *@
<div id="dlgArticle" class="sax-dialog" title="">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmArticle", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.ArticleId, new { @id = "ifArticleId", @class = "ArticleId" })
@Html.HiddenFor(model => model.ArticleExists, new { @id = "ifArticleExists", @class = "ArticleExists" })
@Html.HiddenFor(model => model.ArticleOrganisationId, new { @id = "ifArticleOrganisationId", @class = "ArticleOrganisationId" })
<div class="sax-article">
<div class="sax-article-panel sax-article-header">
<div class="spacer2"></div>
@* Organisation *@
<div class="sax-form-control ArticleOrganisation">
<div class="row">
<div class="col-sm-3">
<div class="sax-label pt4 pr8">
<label for="ifArticleOrganisation"><span>@Localiser["label.organisation"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" id="ifArticleOrganisation" class="ArticleOrganisation sax-input" readonly="readonly" />
</div>
</div>
</div>
</div>
@* Article headline *@
<div class="sax-form-control ArticleHeadline">
<div class="row">
<div class="col">
<div class="sax-label"><label for="ifArticleHeadline"><span>@Localiser["label.headline"]</span><span class="required">&nbsp;*</span></label></div>
</div>
<div class="fixed right"><div class="sax-info"></div></div>
</div>
<div class="sax-control">
@Html.TextBoxFor(model => model.ArticleHeadline, new { @id = "ifArticleHeadline", @class = "ArticleHeadline sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Article_Headline) })
</div>
<div class="sax-message"></div>
</div>
@* Article summary *@
<div class="sax-form-control ArticleSummary">
<div class="row">
<div class="col">
<div class="sax-label"><label for="ifArticleSummary"><span>@Localiser["label.summary"]</span><span class="required">&nbsp;*</span></label></div>
</div>
<div class="fixed right"><div class="sax-info"></div></div>
</div>
<div class="sax-control">
@Html.TextAreaFor(model => model.ArticleSummary, new { @id = "ifArticleSummary", @class = "ArticleSummary sax-textarea", @rows = 6, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Article_Summary) })
</div>
<div class="sax-message"></div>
</div>
@* Sport *@
<div class="sax-form-control ArticleSportId">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifArticleSportId"><span>@Localiser["label.sport"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.ArticleSportId, new { @id = "ifArticleSportId", @class = "ArticleSportId" })
<div class="col">@Html.TextBoxFor(model => model.ArticleSportCaption, new { @id = "ifArticleSportCaption", @class = "ArticleSportCaption sax-input", @readonly = "readonly", @onDblClick = "sax.selectSport()" }) </div>
<div class="fixed"><button class="SelectSport sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Region *@
<div class="sax-form-control ArticleRegionId">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifArticleRegionId"><span>@Localiser["label.region"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.ArticleRegionId, new { @id = "ifArticleRegionId", @class = "ArticleRegionId" })
<div class="col">@Html.TextBoxFor(model => model.ArticleRegionCaption, new { @id = "ifArticleRegionCaption", @class = "ArticleRegionCaption sax-input", @readonly = "readonly", @onDblClick = "sax.selectRegion()" }) </div>
<div class="fixed"><button class="SelectRegion sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Publication *@
<div class="sax-form-control ArticlePublication">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifArticleRegionId"><span>@Localiser["label.publication"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<div class="row ArticlePublish">
@Html.HiddenFor(model => model.ArticlePublished, new { @id = "ifArticlePublished" })
<div class="fixed"><div class="pl16 pt4 pr8">@Html.CheckBoxFor(model => model.ArticlePublish, new { @id = "chkArticlePublish", @class = "ArticlePublish" })&nbsp;<span class="" onclick="$('#chkArticlePublish').click()">@Localiser["label.publish"]</span></div></div>
<div class="col">@Html.TextBoxFor(model => model.ArticlePublishOn, new { @id = "ifArticlePublishOn", @class = "sax-input sax-calendar ArticlePublishOn", @readonly = "readonly" })</div>
</div>
<div class="ArticlePublished">
<div class=""><div class="">@Localiser["label.published"]: <span class="ArticlePublishedOn"></span></div></div>
<div class=""><div class="">@Localiser["label.feedback"]: <span class="ArticleFeedback"></span></div></div>
</div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<div class="spacer2"></div>
</div>
<div class="sax-article-panel sax-article-content">
<div class="sax-article-segment-list">
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item ArticleSegment">
@* Inputs with same name for every segment to ensure they are uploaded as a list. *@
<input type="hidden" name="ArticleContentSegmentBodies" class="ArticleContentSegmentBody" value="" />
<input type="hidden" name="ArticleContentSegmentItemImageIds" class="ArticleContentSegmentItemImageId" value="" />
<input type="hidden" name="ArticleContentSegmentImageIdxs" class="ArticleContentSegmentImageIdx" value="" />
@* Image drop panel *@
<div class="sax-panel-file-drop row">
<div class="col">Drop image here</div>
<div class="fixed">
<img class="ContactImageUploadProgress" src="~/img/animated/progress-basketball-32.gif" style="display:none;margin:8px 4px;" />
</div>
</div>
@* Segment details *@
<div class="row">
@* Segment body *@
<div class="col">
<div class="sax-article-segment-body"></div>
<div class="sax-article-segment-image hidden"><img src="" alt="" title="" /></div>
</div>
<div class="fixed sax-actions">
@* Segment actions *@
<div class="sax-action sax-add"><img src="~/img/action/add24.png" alt="" title="" /></div>
<div class="sax-action sax-add-image"><img src="~/img/action/image-add-24.png" alt="" title="" /></div>
<div class="sax-action sax-up"><img src="~/img/action/upload24.png" alt="" title="" /></div>
<div class="sax-action sax-down"><img src="~/img/action/download24.png" alt="" title="" /></div>
<div class="sax-action sax-delete"><img src="~/img/action/delete24.png" alt="" title="" /></div>
</div>
</div>
</div>
</div>
</div>
</div>
}
</div>
@* Article Image upload dialog and form *@
<div id="dlgSelectArticleImage" class="sax-dialog">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSelectArticleImage", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" class="SegmentIdx" />
<div class="spacer4"></div>
<div class="sax-form-control ArticleImage">
<div class="row">
<div class="fixed"><div style="padding:0 12px;">@Localiser["label.image"]</div></div>
<div class="col"><input type="file" id="ifArticleImage" name="" class="fullwidth" style="" /></div>
</div>
<div class="sax-message"></div>
</div>
<div class="spacer2"></div>
<div class="row">
<div class="col"><div class="UploadMessage"></div></div>
<div class="fixed"><img class="ArticleImageUploadProgress" src="~/img/animated/progress-basketball-32.gif" style="display:none;margin:8px 4px;" /></div>
</div>
<div class="spacer2"></div>
}
</div>
<script type="text/javascript">
sax.dlg.title.imageUpload = '@Localiser.GetJavascriptText("account.caption.image.upload")';
sax.msg.uploadFileSelect = '@Localiser.GetJavascriptText("msg.upload.file.select")';
sax.msg.uploadImageSingle = '@Localiser.GetJavascriptText("msg.upload.image.single")';
sax.msg.uploadFileSizeMax = '@Localiser.GetJavascriptText("msg.upload.file.size.max","{0}")';
sax.msg.uploadFileTypeInvalid = '@Localiser.GetJavascriptText("msg.upload.file.type.invalid","{0}")';
sax.msg.uploadingFile = '@Localiser.GetJavascriptText("msg.upload.file.uploading","{0}")';
</script>
<partial name="~/Views/Common/SportSelect.cshtml" />
<partial name="~/Views/Common/RegionSelect.cshtml" />
@@ -0,0 +1,59 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Code Select */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-code-sel.js");
}
<link rel="stylesheet" href="~/css/sax-code-sel.css?@Model.HrefVersion" />
<div id="dlgCodeSelect" class="sax-dialog sax-form" style="padding-bottom:8px;">
@* Filter *@
<div class=""></div>
<div class="row h-100">
@* Available codes *@
<div class="col-sm-5">
<div class="h-100 sax-codeSelect-codes AvailableCodes"></div>
</div>
@* Code selection *@
<div class="col-sm-2">
<div class="row">
<div class="col-sx-6 col-sm-12 center">
<button class="sax-button Select">@Localiser["label.select"]</button>
</div>
<div class="col-sx-6 col-sm-12 center">
<button class="sax-button Remove">@Localiser["label.remove"]</button>
</div>
</div>
</div>
@* Selected codes *@
<div class="col-sm-5">
<div class="h-100 sax-codeSelect-codes SelectedCodes">
</div>
</div>
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Code">
<div class="CodeDesc"></div>
</div>
</div>
</div>
@@ -0,0 +1,50 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Contact Methods */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-cnt-mth.js");
}
<div id="dlgContactMethod" class="sax-dialog sax-form" title="@Localiser["caption.contact.method"]">
<div class="spacer4"></div>
@* Method *@
<div class="sax-form-control ContactMethod">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.method"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<select name="ContactMethod" class="ContactMethod sax-select" style="max-width:240px;"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<div class="spacer"></div>
@* Value *@
<div class="sax-form-control ContactValue">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.value"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" name="ContactValue" class="ContactValue sax-input" maxlength="200" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<div class="spacer"></div>
</div>
@@ -0,0 +1,98 @@
@* Organisation controls - Address *@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@* NB Organisation address controls may be present more than once on a page, eg. Profile Organisation. *@
@* Organisation address - Line 1 *@
<div class="sax-form-control OrganisationAddressLine1">
<div class="row">
<div class="col-sm-3">
<div class="sax-caption">
<label for=""><span>@Localiser["label.address"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<div class="col"><input type="text" name="Organisation.Address.Line1" class="OrganisationAddressLine1 sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line1)" /> </div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation address - Line 2 *@
<div class="sax-form-control OrganisationAddressLine2">
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm">
<div class="sax-control row">
<div class="col"><input type="text" name="Organisation.Address.Line2" class="OrganisationAddressLine2 sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line2)" /> </div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation address - Line 3 *@
<div class="sax-form-control OrganisationAddressLine3">
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm">
<div class="sax-control row">
<div class="col"><input type="text" name="Organisation.Address.Line3" class="OrganisationAddressLine3 sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Line3)" /> </div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation address - Town *@
<div class="sax-form-control OrganisationAddressTown">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.town"]</span><span class="required">&nbsp;(*)</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<div class="col"><input type="text" name="Organisation.Address.Town" class="OrganisationAddressTown sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Town)" /> </div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation address - Province *@
<div class="sax-form-control OrganisationAddressProvince">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.province"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<div class="col"><input type="text" name="Organisation.Address.Province" class="OrganisationAddressProvince sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_Province)" /> </div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation address - PostalCode *@
<div class="sax-form-control OrganisationAddressPostalCode">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.postal.code"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<div class="col"><input type="text" name="Organisation.Address.PostalCode" class="OrganisationAddressPostalCode sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.ItemAddress_PostalCode)" /> </div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@@ -0,0 +1,77 @@
@* Organisation controls - Contact *@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@* NB Organisation contact controls may be present more than once on a page, eg. Profile Organisation. *@
@if (!Model.RegisterView(ViewContext)) return;
<style>
.OrganisationContactMethods .ContactMethods
{
min-height: 32px;
max-height: 64px;
background-color: #fff;
border: 1px solid #808080;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
overflow-y: scroll;
}
.OrganisationContactMethods .ContactMethods table.sax-data tr:hover
{
background-color: #f0f0f0;
}
.OrganisationContactMethods .ContactMethods table.sax-data .sax-label
{
margin: 0;
padding: 0 8px 0 0;
}
</style>
@* Organisation Contact Methods *@
<div class="sax-form-control OrganisationContactMethods">
<input type="hidden" name="Organisation.ContactMethods" value="0" />
<div class="row">
<div class="col-sm-3 row">
<div class="col">
<div class="sax-label">
<label for=""><span>@Localiser["label.contact"]</span></label>
</div>
</div>
<div class="fixed">
@* Action to add new contact method. *@
<div class="sax-actions">
<div class="sax-action sax-add pr4"><img src="~/img/action/add24.png" alt="@(Localiser["label.add"])" title="@(Localiser["label.add"])" /></div>
</div>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<div class="col">
@* List of existing contact methods with actions to edit/remove. *@
<div class="ContactMethods">
<table class="sax-data fullwidth"></table>
</div>
<table class="sax-panel-item-template">
<tr class="ContactMethod">
<td style="width:20px;"><div class="sax-label nowrap"></div></td>
<td class="sax-value " style="width:70%;word-break: break-all;"></td>
<td class="sax-actions right nowrap" style="min-width:10px;">
<input type="hidden" name="Organisation.ContactMethod" />
<input type="hidden" name="Organisation.ContactValue" />
<div class="nowrap">
<div class="inlineb sax-action sax-edit"><img src="~/img/action/edit16.png" alt="@(Localiser["label.edit"])" title="@(Localiser["label.edit"])" /></div>
<div class="inlineb sax-action sax-remove"><img src="~/img/action/delete16.png" alt="@(Localiser["label.remove"])" title="@(Localiser["label.remove"])" /></div>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<partial name="~/Views/Common/ContactMethods.cshtml" />
@@ -0,0 +1,93 @@
@* Organisation controls - Core *@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@* NB Core Organisation controls may be present more than once on a page, eg. Profile Organisation. *@
@* Organisation name *@
<div class="sax-form-control OrganisationName">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.name"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" name="Organisation.Name" class="OrganisationName sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.Organisation_Name)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm row">
@* Organisation category *@
<div class="col-sm-6">
<div class="sax-form-control OrganisationCategory pr4">
<div class="sax-label">
<label for=""><span>@Localiser["label.category"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
@Html.DropDownList("", Model.OrganisationCategoriesForSelect, Localiser["label.select"] + ":", new { @id = "", @name = "Organisation.Category", @class = "OrganisationCategory sax-select" })
</div>
<div class="sax-message"></div>
</div>
</div>
@* Organisation type *@
<div class="col-sm-6">
<div class="sax-form-control OrganisationType">
<div class="sax-label">
<label for=""><span>@Localiser["label.type"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
@Html.DropDownList("", Model.OrganisationTypesForSelect, Localiser["label.select"] + ":", new { @id = "", @name = "Organisation.Type", @class = "OrganisationType sax-select" })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
@* Organisation sport *@
<div class="sax-form-control OrganisationSportId">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.sport"]</span><span class="required">&nbsp;(*)</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<input type="hidden" name="Organisation.SportId" class="OrganisationSportId" />
<div class="col"><input type="text" class="OrganisationSportName sax-input" readonly="readonly" /> </div>
<div class="fixed"><button class="SelectOrganisationSport sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Organisation primary region *@
<div class="sax-form-control OrganisationRegionIdPrimary">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.region"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<input type="hidden" name="Organisation.RegionIdPrimary" class="OrganisationRegionIdPrimary" />
<div class="col"><input type="text" class="OrganisationRegionNamePrimary sax-input" readonly="readonly" /> </div>
<div class="fixed"><button class="SelectOrganisationRegionPrimary sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@@ -0,0 +1,103 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Organisation Request. */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-org-req.js");
Microsoft.AspNetCore.Html.HtmlString regionIdCountry = new Microsoft.AspNetCore.Html.HtmlString(Model.ValidUserSession ? Model.GetEncryptedRegionId(Model.SessionData.User.RegionIdCountry) : "");
String regionNameCountry = Model.ValidUserSession ? Localiser.EscapeJavascriptText(Model.GetRegionCaption(Model.SessionData.User.RegionIdCountry)) : "";
}
<script type="text/javascript">
let or = sax.orgReq = {};
or.dlgTitleRequestOrganisation = '@Localiser.GetJavascriptText("organisation.caption.organisation.request")';
sax.data.regionIdCountryDefault = '@regionIdCountry';
sax.data.regionNameCountryDefault = '@regionNameCountry';
</script>
<div id="dlgOrganisationRequest" class="sax-dialog sax-form">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationRequest", @class = "sax-form", @onSubmit = "return false;" }))
{
@* Contact details are only required when user not logged in. *@
@if (!Model.ValidUserSession)
{
<div class="sax-form-control pt8">
<div class="sax-caption">
<label for="ifContactEmailAddress"><span>@Localiser["label.contact.details"]</span></label>
</div>
</div>
@* Contact Email Address *@
<div class="sax-form-control ContactEmailAddress">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifContactEmailAddress"><span>@Localiser["label.email"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" id="ifContactEmailAddress" name="ContactEmailAddress" class="ContactEmailAddress sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-3">@* No label. *@</div>
<div class="col-sm row">
@* Contact First Name *@
<div class="col-sm-6">
<div class="sax-form-control ContactFirstName pr4">
<div class="sax-label">
<label for="cbContactFirstName"><span>@Localiser["label.first.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" id="ifContactFirstName" name="ContactFirstName" class="ContactFirstName sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)" />
</div>
<div class="sax-message"></div>
</div>
</div>
@* Contact Last Name *@
<div class="col-sm-6">
<div class="sax-form-control ContactLastName">
<div class="sax-label">
<label for="cbContactLastName"><span>@Localiser["label.last.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" id="ifContactLastName" name="ContactLastName" class="ContactLastName sax-input" autocomplete="off" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
}
@* Organisation core details - name, category, sport, region etc *@
<div class="sax-form-control">
<div class="sax-caption">
<label for=""><span>@Localiser["label.organsiation.details"]</span></label>
</div>
</div>
<partial name="~/Views/Common/OrganisationControls-Core.cshtml" />
@* Organisation Address *@
<partial name="~/Views/Common/OrganisationControls-Address.cshtml" />
}
</div>
<partial name="~/Views/Common/SportSelect.cshtml" />
<partial name="~/Views/Common/RegionSelect.cshtml" />
@@ -0,0 +1,52 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Organisation Select. */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-org-sel.js");
}
<link rel="stylesheet" href="~/css/sax-org-sel.css?@Model.HrefVersion" />
<script type="text/javascript">
sax.selectOrganisation = sax.selectOrganisation || {};
sax.selectOrganisation.dlgTitleSelectOrganisation = '@Localiser.GetJavascriptText("organisation.caption.organisation.select")';
</script>
<div id="dlgSelectOrganisation" class="sax-dialog sax-form">
@* Filter *@
<div class="sax-panel-filter">
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmSelectOrganisationFilter", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" class="FilterRegionId" name="Filter.RegionId" />
<input type="hidden" class="FilterSportId" name="Filter.SportId" />
<input type="hidden" class="FilterPageLength" name="DataPageLength" value="0" />
<input type="hidden" class="FilterPageNo" name="DataPageNo" value="0" />
<div class="row">
<div class="col-sm-5 sax-label text-left text-sm-right">@Localiser["label.search"]</div>
<div class="col-sm-7"><input type="search" class="sax-input FilterOrganisation" name="Filter.Organisation" maxlength="32" autocomplete="off" /></div>
</div>
}
</div>
</div>
<div class="">
@* Organisations *@
<div class="sax-selection-results Organisations"></div>
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Organisation">
<div class="Name"></div>
</div>
</div>
</div>
@@ -0,0 +1,56 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Region Select */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-reg-sel.js");
}
<link rel="stylesheet" href="~/css/sax-reg-sel.css?@Model.HrefVersion" />
<script type="text/javascript">
</script>
<div id="dlgSelectRegion" class="sax-dialog sax-form">
<div class="container-fluid h-100">
@* Selected Region *@
<div class="SelectedRegion">
<div class="row">
<div class="col-12"><div class="sax-label">@Localiser["label.region.selected"]:</div></div>
<div class="col-12"><div class="SelectedRegionName"></div></div>
</div>
<div class="spacer2"></div>
</div>
@* Region Filter *@
<div class="RegionFilter sax-form">
<div class="row">
<div class="col right"><div class="sax-label">@Localiser["label.search"]</div></div>
<div class="fixed"><input type="search" id="ifRegionFilter" class="sax-input" maxlength="16" autocomplete="off" onkeyup="sax.mdl.selectRegion.fn.displayRegions();" /></div>
</div>
<div class="spacer2"></div>
</div>
@* Available regions *@
<div class="row">
<div class="col"><div class="sax-label bottom">@Localiser["label.regions.available"]:</div></div>
</div>
<div class="row">
<div class="col-12 sax-selectRegion-regions AvailableRegions"></div>
</div>
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Region">
<div class="RegionName"></div>
</div>
</div>
</div>
@@ -0,0 +1,27 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Results Paging. */
// NB View can be included more than once on a page.
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-res-pag.js");
}
<script type="text/javascript">
sax.mdl.resultsPaging = sax.mdl.resultsPaging || {msg:{}};
sax.mdl.resultsPaging.msg.pageInfo = '@Localiser.GetJavascriptText("label.page.info","{0}","{1}")';
</script>
@* Results paging *@
<div class="sax-results-paging hidden">
@Html.HiddenFor(model => model.Paging.PageNoValue, new { @id = "", @class = "PagingPageNo" })
@Html.HiddenFor(model => model.Paging.PageLengthValue, new { @id = "", @class = "PagingPageLength" })
<div class="iblock sax-action sax-paging-first"><img src="~/img/action/first24.png" alt="@(Localiser["label.first"])" title="@(Localiser["label.first"])" /></div>
<div class="iblock sax-action sax-paging-prev"><img src="~/img/action/prev24.png" alt="@(Localiser["label.previous"])" title="@(Localiser["label.previous"])" /></div>
<div class="iblock sax-paging-info"></div>
<div class="iblock sax-action sax-paging-next"><img src="~/img/action/next24.png" alt="@(Localiser["label.next"])" title="@(Localiser["label.next"])" /></div>
<div class="iblock sax-action sax-paging-last"><img src="~/img/action/last24.png" alt="@(Localiser["label.last"])" title="@(Localiser["label.last"])" /></div>
</div>
@@ -0,0 +1,106 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
// Sport Contacts.
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-spt-cnt.js");
}
<link rel="stylesheet" href="~/css/sax-spt-cnt.css?@Model.HrefVersion" />
<div id="dlgSportContact" class="sax-dialog sax-form" style="padding-bottom:8px;">
<div class="spacer4"></div>
@using (Html.BeginForm(FormMethod.Post, new { @id = "frmProfileSportContact", @class = "sax-form", @onSubmit = "return false;" }))
{
@* Contact type *@
<div class="sax-form-control ContactType">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for=""><span>@Localiser["label.contact"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<select name="SportContact.Type" class="ContactType sax-select" style="max-width:240px;"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
<div class="spacer"></div>
@* User email *@
<div class="sax-form-control ContactEmailAddress">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifContactEmailAddress"><span>@Localiser["label.email"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" id="ifContactEmailAddress" name="SportContact.EmailAddress" class="ContactEmailAddress sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Contact Name *@
<div class="row">
<div class="col-sm-3">
<div class="sax-label d-none d-sm-block">
<label for="ifContactFirstName"><span>@Localiser["label.name"]</span></label>
</div>
</div>
<div class="col-sm row">
<div class="col-sm-4">
@* Contact Title *@
<div class="sax-form-control ContactTitle pr8">
<div class="sax-label">
<label for="cbContactTitle"><span>@Localiser["label.title"]</span></label>
</div>
<div class="sax-control">
<select name="SportContact.Title" class="ContactTitle sax-select"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-sm-4">
@* Contact first name *@
<div class="sax-form-control ContactFirstName pr8">
<div class="sax-label">
<label for="ifContactFirstName"><span>@Localiser["label.first.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" name="SportContact.FirstName" class="ContactFirstName sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName)" />
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-sm-4">
@* Contact last name *@
<div class="sax-form-control ContactLastName">
<div class="sax-label">
<label for="ifContactLastName"><span>@Localiser["label.last.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" name="SportContact.LastName" class="ContactLastName sax-input" maxlength="@Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName)" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
}
<div class="spacer"></div>
</div>
@@ -0,0 +1,80 @@
@* Organisation controls - Contact *@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@* NB Organisation contact controls may be present more than once on a page, eg. Profile Organisation. *@
@if (!Model.RegisterView(ViewContext)) return;
<style>
.SportContacts .Contacts
{
min-height: 32px;
max-height: 64px;
background-color: #fff;
border: 1px solid #808080;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
overflow-y: scroll;
}
.SportContacts.Contacts table.sax-data tr:hover
{
background-color: #f0f0f0;
}
.SportContacts .Contacts table.sax-data .sax-label
{
margin: 0;
padding: 0 8px 0 0;
}
</style>
@* Sport Contacts *@
<div class="sax-form-control SportContacts">
<input type="hidden" name="Contacts.Count" value="0" />
<div class="row">
<div class="col-sm-3 row">
<div class="col">
<div class="sax-label">
<label for=""><span>@Localiser["label.contacts"]</span></label>
</div>
</div>
<div class="fixed">
@* Action to add new contact. *@
<div class="sax-actions">
<div class="sax-action sax-add pr4"><img src="~/img/action/add24.png" alt="@(Localiser["label.add"])" title="@(Localiser["label.add"])" /></div>
</div>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<div class="col">
@* List of existing contacts with actions to edit/remove. *@
<div class="Contacts">
<table class="sax-data fullwidth"></table>
</div>
<table class="sax-panel-item-template">
<tr class="Contact">
<td style="width:20px;"><div class="sax-label nowrap"></div></td>
<td class="sax-value Name" style="width:40%;word-break: break-all;"></td>
<td class="sax-value Details" style="width:40%;overflow-x:hidden;"></td>
<td class="sax-actions right nowrap" style="min-width:10px;">
<input type="hidden" name="Contacts.ContactType" />
<input type="hidden" name="Contacts.Title" />
<input type="hidden" name="Contacts.FirstName" />
<input type="hidden" name="Contacts.LastName" />
<input type="hidden" name="Contacts.Phone" />
<input type="hidden" name="Contacts.EmailAddress" />
<div class="nowrap">
<div class="inlineb sax-action sax-edit"><img src="~/img/action/edit16.png" alt="@(Localiser["label.edit"])" title="@(Localiser["label.edit"])" /></div>
<div class="inlineb sax-action sax-remove"><img src="~/img/action/delete16.png" alt="@(Localiser["label.remove"])" title="@(Localiser["label.remove"])" /></div>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@@ -0,0 +1,53 @@
@model SaxModel_Online
@inject SaxLocaliser Localiser
@{
/* Online Sport Select */
// Ensure view is only included once in page.
if (!Model.RegisterView(ViewContext)) return;
// Scripts.
Model.AdditionalFiles_JS.Add("js/sax-spt-sel.js");
}
<link rel="stylesheet" href="~/css/sax-spt-sel.css?@Model.HrefVersion" />
<div id="dlgSelectSport" class="sax-dialog sax-form">
<div class="container-fluid h-100">
@* Selected Sport *@
<div class="SelectedSport">
<div class="row">
<div class="col-12"><div class="sax-label">@Localiser["label.sport.selected"]</div></div>
<div class="col-12"><div class="SelectedSportName"></div></div>
</div>
<div class="spacer2"></div>
</div>
@* Sport Filter *@
<div class="SportFilter sax-form">
<div class="row">
<div class="col right"><div class="sax-label">@Localiser["label.search"]</div></div>
<div class="fixed"><input type="search" id="ifSportFilter" class="sax-input" maxlength="16" autocomplete="off" oninput="sax.mdl.selectSport.fn.displaySports();" /></div>
</div>
<div class="spacer2"></div>
</div>
@* Available sports *@
<div class="row">
<div class="col"><div class="sax-label bottom">@Localiser["label.sports.available"]:</div></div>
</div>
<div class="row">
<div class="col-12 sax-selectSport-sports AvailableSports"></div>
</div>
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Sport">
<div class="SportName"></div>
</div>
</div>
</div>
@@ -0,0 +1,152 @@
@model Model_Home
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Home";
}
@if (Model.AwaitingLogin)
{
@* No user session *@
<div class="text-center">
<h1 class="display-4">@Localiser["home.caption.welcome"]</h1>
</div>
<div class="spacer8"></div>
@* Register Organisation *@
@if (true)
{
<div class="sax-panel-info">
<div class="sax-content">
<div class="sax-para">@Localiser["home.organisation.request.para1"]</div>
<div class="sax-para">@Localiser["home.organisation.request.para2"]</div>
<div class="sax-para center"><div class="sax-button-cta" onclick="sax.orgReq.fn.requestOrganisation()">@Localiser["home.organisation.request.action"]</div></div>
</div>
</div>
<partial name="~/Views/Common/OrganisationRequest.cshtml" />
}
}
else
{
@* User logged in *@
@* Activity filter *@
<div class="sax-panel-filter">
<div class="sax-header row">
<div class="col"><div class="sax-caption">@Localiser["home.caption.activities"]</div></div>
<div class="fixed row">
</div>
</div>
<div class="sax-content">
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmActivityFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
<div class="row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm-8"><input type="search" name="Filter.Search" id="ifFilterSearch" class="FilterSearch sax-input" maxlength="64" /></div>
</div>
}
</div>
</div>
@* Results panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
@* Item templates *@
<div class="sax-panel-item-template">
@* Activity template *@
<div class="sax-panel-item Activity">
<div class="sax-header row">
<div class="col">
<div class="sax-caption">
<div class="row">
<div class="col"><div class="sax-action ActivityCaption"></div></div>
<div class="fixed row">
<div class="col sax-action ActivityOrganisation"></div>
<div class="fixed right ActivityReaction"></div>
</div>
</div>
</div>
</div>
<div class="fixed sax-actions">
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="fixed ActivityThumbnail"><img src="" class="" alt="" title="" style="max-width:@(Sax.Config.Activities.Thumbnail.DisplayWidthMax_List + "px");max-height:@(Sax.Config.Activities.Thumbnail.DisplayHeightMax_List + "px");padding:0 4px 0 0;" /></div>
<div class="col">
<div class="row">
<div class="col-lg">
<div class="ActivityBlurb"></div>
<div class="sax-note"><span class="ActivitySport"></span> - <span class="ActivityRegion"></span></div>
</div>
<div class="fixed">
<div class="sax-note ActivityAuthor"></div>
<div class="sax-note ActivityPublished">@Localiser["label.published"]:&nbsp;<span class="ActivityPublishedOn"></span></div>
</div>
</div>
</div>
<div class="fixed ActivityOrganisationLogo"><img src="" class="" alt="" title="" style="max-width:@(Sax.Config.Organisation.Logo.DisplayWidthMax_List + "px");max-height:@(Sax.Config.Organisation.Logo.DisplayHeightMax_List + "px");padding:0 0 0 4px;" /></div>
</div>
</div>
</div>
</div>
</div>
@* Activity Id form - to retrieve details for the selected activity. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmActivityId", @class = "", @action = "#", onsubmit = "return false;" }))
{
<input type="hidden" name="ActivityItemType" class="ActivityItemType" />
<input type="hidden" name="ActivityItemId" class="ActivityItemId" />
}
@* Activity React form - to record reaction to the selected activity. *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmActivityReact", @class = "", @action = "#", onsubmit = "return false;" }))
{
<input type="hidden" name="ActivityItemType" class="ActivityItemType" />
<input type="hidden" name="ActivityItemId" class="ActivityItemId" />
<input type="hidden" name="ActivityReaction" class="ActivityReaction" />
}
@* Activity popup dialog with details. *@
<div id="dlgActivity" class="sax-dialog">
<div class="sax-content">
</div>
<div class="sax-panel-item-template">
<div class="sax-panel-item Article">
<div class="row">
<div class="col">
<div class="ArticleHeadline"></div>
<div class="ArticleSummary"></div>
</div>
<div class="fixed ArticleOrganisationLogo"><img src="" class="" alt="" title="" style="max-width:@(Sax.Config.Organisation.Logo.DisplayWidthMax_Item + "px");max-height:@(Sax.Config.Organisation.Logo.DisplayHeightMax_Item + "px");padding:0 0 4px 4px;" /></div>
</div>
<div class="ArticleOrigin">
<div class="row">
<div class="col"><div class="ArticleAuthor"></div></div>
<div class="fixed right"><div class="ArticleOrganisation"></div></div>
</div>
</div>
<div class="ArticleContent">
<div class="ArticleContentSegments"></div>
</div>
</div>
<div class="sax-panel-item ArticleContentSegment">
</div>
<div class="sax-panel-item ArticleContentSegmentImage center">
<img src="" alt="" title="" />
</div>
<div class="sax-panel-item Event">
<div class="EventOrganisation"></div>
<div class="EventAt"></div>
<div class="EventSummary"></div>
<div class="EventDetails"></div>
</div>
<div class="sax-panel-item Opportunity">
<div class="OpportunityOrganisation"></div>
<div class="OpportunityPosition"></div>
<div class="OpportunityOutline"></div>
</div>
</div>
</div>
}
@@ -0,0 +1,118 @@
@model Model_Organisation_Events
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Organisation - Events";
}
<div class="sax-panel Events">
<script type="text/javascript">
sax.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
sax.lbl.copy = '@(Localiser.GetJavascriptText("label.copy"))';
</script>
@* Filter Form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmEventFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
@* Filter Panel *@
<div class="sax-panel-filter">
@* Filter header *@
<div class="sax-header row">
<div class="col"><div class="sax-caption">@Localiser["organisation.caption.events"]</div></div>
<div class="fixed row">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="sax-control row">
<div class="col-sm-3"><div class="sax-label text-left text-sm-right">@Localiser["label.organisation"]</div></div>
<div class="col-sm-9 row">
@Html.HiddenFor(model => model.Filter.OrganisationId, new { @id = "ifFilterOrganisationId", @class = "FilterOrganisationId" })
<div class="col"><input type="text" id="ifFilterOrganisation" class="FilterOrganisation sax-input" readonly="readonly" value="@(Model.Filter.OrganisationCaption)" /></div>
<div class="fixed"><button class="sax-button SelectFilterOrganisation">@Localiser["label.select"]</button></div>
</div>
</div>
<div class="sax-control row">
<div class="col-sm-3"><div class="sax-label text-left text-sm-right">@Localiser["label.period"]</div></div>
<div class="col-sm-3">@Html.DropDownListFor(model => model.Filter.Period.Type, Model.EventPeriodTypesForDropdown, new { @id = "cbFilterPeriodType", @class = "FilterPeriodType sax-select" })</div>
<div class="col-sm-3"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm-3"><input type="search" id="ifEventFilter" name="Filter.Event" class="EventFilter sax-input" maxlength="64" autocomplete="off" /></div>
</div>
</div>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Events">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
}
@* Results Panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-table">
<div class="sax-panel-info" style="min-height:120px;max-height:100%;overflow-y:auto;">
<table class="sax-item-list">
<thead>
<tr>
<th class="center">@Localiser["label.date"]</th>
<th class="center">@Localiser["label.time"]</th>
<th class="">@Localiser["label.summary"]</th>
<th class="center">@Localiser["label.published"]</th>
<th class="center">@Localiser["label.actions"]</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
@* Element templates *@
<div class="sax-panel-item-template">
@* Item template *@
<table>
<tr class="sax-row-item Event">
<td><div class="EventAtDate center"></div></td>
<td><div class="EventAtTime center"></div></td>
<td><div class="EventSummary pointer"></div></td>
<td><div class="EventPublishedOn center"></div></td>
<td>
<div class="EventActions center">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</td>
</tr>
</table>
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Event">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
<partial name="~/Views/Common/OrganisationSelect.cshtml" />
<partial name="Panel-EventDialog.cshtml" />
@@ -0,0 +1,117 @@
@model Model_Organisation_Members
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Organisation - Members";
}
<div class="sax-panel Members">
<script type="text/javascript">
sax.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
sax.lbl.remove = '@(Localiser.GetJavascriptText("label.remove"))';
sax.lbl.activate = '@(Localiser.GetJavascriptText("label.activate"))';
sax.msg.activate = '@(Localiser.GetJavascriptText("account.activate.info"))';
sax.dlg.title.remove = '@(Localiser.GetJavascriptText("organisation.member.remove"))';
sax.dlg.title.selectRoles = '@(Localiser.GetJavascriptText("organisation.member.select.roles"))';
sax.msg.remove= '@(Localiser.GetJavascriptText("organisation.member.remove.confirm"))';
sax.dlg.title.activate = '@(Localiser.GetJavascriptText("account.activate.caption"))';
</script>
@* Filter Form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmMemberFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
@* Filter panel *@
<div class="sax-panel-filter">
@* Filter header *@
<div class="sax-header row">
@* Filter caption *@
<div class="col"><div class="sax-caption">@Localiser["organisation.members"]</div></div>
<div class="fixed row">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.organisation"]</div></div>
<div class="col-sm-8 row">
@Html.HiddenFor(model => model.Filter.OrganisationId, new { @id = "ifFilterOrganisationId", @class = "FilterOrganisationId" })
<div class="col"><input type="text" id="ifFilterOrganisation" class="FilterOrganisation sax-input" value="@(Model.Filter.OrganisationCaption)" /></div>
<div class="fixed"><button class="sax-button SelectFilterOrganisation">@Localiser["label.select"]</button></div>
</div>
</div>
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm-8"><input type="search" id="ifMemberFilter" name="Filter.Member" class="MemberFilter sax-input" maxlength="64" autocomplete="off" /></div>
</div>
</div>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Members">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
}
@* Results Panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
@* Element templates *@
<div class="sax-panel-item-template">
@* Item template *@
<div class="sax-panel-item Member">
<div class="sax-header row">
<div class="col">
@* Member caption *@
<div class="sax-caption"><div class="sax-action"><span class="MemberDisplayName"></span></div></div>
</div>
<div class="fixed sax-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-6"><div class="MemberEmail"></div></div>
<div class="col-sm-6 text-sm-right"><div class="MemberRoles"></div></div>
</div>
<div class="row">
<div class="col-sm-8"><div class="MemberAdminComment sax-note"></div></div>
<div class="col-sm-4 text-sm-right"><div class="MemberStatus sax-note"></div></div>
</div>
</div>
</div>
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Member">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
<partial name="Panel-MemberDialog.cshtml" />
<partial name="~/Views/Common/OrganisationSelect.cshtml" />
<partial name="~/Views/Common/CodeSelect.cshtml" />
@@ -0,0 +1,109 @@
@model Model_Organisation_Opportunities
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Organisation - Opportunities";
}
<div class="sax-panel Opportunities">
<script type="text/javascript">
sax.lbl.edit = '@(Localiser.GetJavascriptText("label.edit"))';
</script>
@* Filter Form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOpportunityFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
@* Filter Panel *@
<div class="sax-panel-filter">
@* Filter header *@
<div class="sax-header row">
<div class="col"><div class="sax-caption">@Localiser["organisation.caption.opportunities"]</div></div>
<div class="fixed row">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.organisation"]</div></div>
<div class="col-sm-8 row">
@Html.HiddenFor(model => model.Filter.OrganisationId, new { @id = "ifFilterOrganisationId", @class = "FilterOrganisationId" })
<div class="col"><input type="text" id="ifFilterOrganisation" class="FilterOrganisation sax-input" readonly="readonly" value="@(Model.Filter.OrganisationCaption)" /></div>
<div class="fixed"><button class="sax-button SelectFilterOrganisation">@Localiser["label.select"]</button></div>
</div>
</div>
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm-8"><input type="search" id="ifOpportunityFilter" name="Filter.Opportunity" class="OpportunityFilter sax-input" maxlength="64" autocomplete="off" /></div>
</div>
</div>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Opportunities">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
}
@* Results Panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
@* Element templates *@
<div class="sax-panel-item-template">
@* Item template *@
<div class="sax-panel-item Opportunity">
<div class="sax-header row">
<div class="col">
<div class="sax-caption"><div class="sax-action"><span class="OpportunityType"></span> - <span class="OpportunityPosition"></span></div></div>
</div>
<div class="fixed sax-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-12"><div class="OpportunityOutline"></div></div>
<div class="col-sm-6"><div class="OpportunityRegionCaption sax-note"></div></div>
<div class="col-sm-6 right"><div class="OpportunitySportCaption sax-note"></div></div>
</div>
<div class="row">
<div class="col-sm-4"><div class="sax-note">@Localiser["label.created"]:&nbsp;<span class="OpportunityCreatedOn"></span></div></div>
<div class="col-sm-4"><div class="OpportunityPublished sax-note">@Localiser["label.published"]:&nbsp;<span class="OpportunityPublishedOn"></span></div></div>
<div class="col-sm-4 right"><div class="OpportunityStatusDesc sax-note"></div></div>
</div>
</div>
</div>
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Opportunity">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
<partial name="~/Views/Common/OrganisationSelect.cshtml" />
<partial name="Panel-OpportunityDialog.cshtml" />
@@ -0,0 +1,198 @@
@model Model_Organisation_Events
@inject SaxLocaliser Localiser
@{
/* Organisation Events - Event dialog panel */
//Model.AdditionalFiles_CSS.Add("lib/jquery-timepicker/jquery.timepicker.min.css");
//Model.AdditionalFiles_JS.Add("lib/jquery-timepicker/jquery.timepicker.min.js");
//Model.AdditionalFiles_CSS.Add("lib/bootstrap4-datetimepicker/bootstrap-datetimepicker.min.css");
//Model.AdditionalFiles_JS.Add("lib/moment.js/moment.min.js");
//Model.AdditionalFiles_JS.Add("lib/bootstrap4-datetimepicker/bootstrap-datetimepicker.min.js");
}
<script type="text/javascript">
sax.dlg.title.createEvent = '@Localiser.GetJavascriptText("organisation.caption.event.create")';
sax.dlg.title.updateEvent = '@Localiser.GetJavascriptText("organisation.caption.event.update")';
sax.dlg.title.selectRegion = '@Localiser.GetJavascriptText("reference.caption.region.select")';
sax.dlg.title.selectSport = '@Localiser.GetJavascriptText("reference.caption.sport.select")';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmEventId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.EventId, new { @class = "EventId" })
}
<div id="dlgEvent" class="sax-dialog" title="">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmEvent", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.EventId, new { @id = "ifEventId", @class = "EventId" })
@Html.HiddenFor(model => model.EventExists, new { @id = "ifEventExists", @class = "EventExists" })
<input type="hidden" id="ifEventOrganisationId" class="EventOrganisationId" name="EventOrganisationId" value="@(Model.Filter.OrganisationId)" />
@* Event type *@
<div class="sax-form-control EventTypeCode">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="cbEventType"><span>@Localiser["label.type"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<select id="cbEventType" class="EventType sax-select" name="EventTypeCode"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Event At date and time *@
<div class="sax-form-control EventAtDate">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifEventAtDate"><span>@Localiser["label.occurs"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="row">
<div class="col-sm">
<div class="sax-control">
<input id="ifEventAtDate" name="EventAtDate" class="EventAtDate sax-input sax-calendar" readonly="readonly" />
</div>
<div class="sax-message"></div>
</div>
<div class="col-sm">
<div class="row">
<div class="col pl-sm-4 pt-2">
@Html.CheckBoxFor(model => model.EventAtTimeSet, new { @id = "chkEventAtTimeSet", @class = "sax-checkbox" })
&nbsp;
<span class="sax-label pointer" onclick="$('#chkEventAtTimeSet').click()">@Localiser["label.starts"]</span>
</div>
<div class="col">
<div class="sax-control">
<input type="time" id="ifEventAtTime" name="EventAtTime" class="EventAtTime sax-input" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@* Event To date and time *@
<div class="sax-form-control EventToDate">
<div class="row">
<div class="col-sm-3 row">
<div class="col">
<div class="sax-label"><span>@Localiser["label.until"]</span></div>
</div>
<div class="fixed">
<div class="pt-2 pr-2">
@Html.CheckBoxFor(model => model.EventToDateSet, new { @id = "chkEventToDateSet", @class = "sax-checkbox" })
</div>
</div>
</div>
<div class="col-sm">
<div class="row">
<div class="col-sm">
<div class="sax-control">
<input id="ifEventToDate" name="EventToDate" class="EventToDate sax-input sax-calendar" readonly="readonly" />
</div>
<div class="sax-message"></div>
</div>
<div class="col-sm">
<div class="row">
<div class="col pl-sm-4 pt-2">
@Html.CheckBoxFor(model => model.EventToTimeSet, new { @id = "chkEventToTimeSet", @class = "sax-checkbox" })
&nbsp;
<span class="sax-label pointer" onclick="$('#chkEventToTimeSet').click()">@Localiser["label.finishes"]</span>
</div>
<div class="col">
<div class="sax-control">
<input type="time" id="ifEventToTime" name="EventToTime" class="EventToTime sax-input" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@* Event summary *@
<div class="sax-form-control EventSummary">
<div class="sax-label">
<label for="ifEventSummary"><span>@Localiser["label.summary"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
@Html.TextAreaFor(model => model.EventSummary, new
{
@id = "ifEventSummary",
@class = "EventSummary sax-textarea",
@rows = 3,
@maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationEvent_Summary)
})
</div>
<div class="sax-message"></div>
</div>
@* Event details *@
<div class="sax-form-control EventDetails">
<div class="sax-label">
<label for="ifEventDetails"><span>@Localiser["label.details"]</span><span class="required">&nbsp;</span></label>
</div>
<div class="sax-control">
@Html.TextAreaFor(model => model.EventDetails, new { @id = "ifEventDetails", @class = "EventDetails sax-textarea", @rows = 6, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationEvent_Details) })
</div>
<div class="sax-message"></div>
</div>
@* Sport *@
<div class="sax-form-control EventSportPrimary">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifEventSportPrimaryCaption"><span>@Localiser["label.sport"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.EventSportIdPrimary, new { @id = "ifEventSportIdPrimary", @class = "EventSportIdPrimary" })
<div class="col"><input type="text" id="ifEventSportCaptionPrimary" class="EventSportCaptionPrimary sax-input" readonly="readonly" /></div>
<div class="fixed"><button class="SelectEventSportPrimary sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Region *@
<div class="sax-form-control EventRegionCountry">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifEventRegionCountryCaption"><span>@Localiser["label.region"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.EventRegionIdCountry, new { @id = "ifEventRegionIdCountry", @class = "EventRegionIdCountry" })
<div class="col"><input type="text" id="ifEventRegionCaptionCountry" class="EventRegionCaptionCountry sax-input" readonly="readonly" /></div>
<div class="fixed"><button class="SelectEventRegionCountry sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
<partial name="~/Views/Common/RegionSelect.cshtml" />
<partial name="~/Views/Common/SportSelect.cshtml" />
@@ -0,0 +1,149 @@
@model Model_Organisation_Members
@inject SaxLocaliser Localiser
@{
/* Organisation Members - Member dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createMember = '@Localiser.GetJavascriptText("caption.member.create")';
sax.dlg.title.updateMember = '@Localiser.GetJavascriptText("caption.member.update")';
sax.dlg.title.selectRegion = '@Localiser.GetJavascriptText("reference.caption.region.select")';
sax.dlg.title.selectSport = '@Localiser.GetJavascriptText("reference.caption.sport.select")';
sax.data.userRoleCodeDelim = '@Model.MemberRoleCodeDelim';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmMemberId", @class = "sax-form" }))
{
<input type="hidden" name="MemberId" class="MemberId" />
}
<div id="dlgMember" class="sax-dialog" title="">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmMember", @class = "sax-form", @onSubmit = "return false;" }))
{
<input type="hidden" name="MemberId" class="MemberId" />
<input type="hidden" name="MemberExists" class="MemberExists" />
<input type="hidden" name="MemberNameSupplied" class="MemberNameSupplied" />
<input type="hidden" class="MemberOrganisationId" name="MemberOrganisationId" value="@(Model.Filter.OrganisationId)" />
@* Member Email Address *@
<div class="sax-form-control MemberEmailAddress">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifMemberEmailAddress"><span>@Localiser["label.email"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" name="MemberEmailAddress" class="MemberEmailAddress sax-input" autocomplete="off" readonly="readonly" maxlength="@(Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress))" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Member Display Name *@
<div class="sax-form-control MemberDisplayName">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifMemberDisplayName"><span>@Localiser["label.name"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<input type="text" if="ifMemberDisplayName" class="MemberDisplayName sax-input" readonly="readonly" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Member Name *@
<div class="row">
<div class="col-sm-3"></div>
<div class="col-sm">
<div class="row">
<div class="col-sm-4">
@* User Title *@
<div class="sax-form-control MemberTitle pr-sm-1">
<div class="sax-label">
<label for="cbMemberTitle"><span>@Localiser["label.title"]</span></label>
</div>
<div class="sax-control">
<select id="cbMemberTitle" name="MemberTitle" class="MemberTitle sax-input"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-sm-4">
@* User First Name *@
<div class="sax-form-control MemberFirstName pr-sm-1">
<div class="sax-label">
<label for="ifMemberFirstName"><span>@Localiser["label.first.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" id="ifMemberFirstName" name="MemberFirstName" class="MemberFirstName sax-input" autocomplete="off" maxlength="@(Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName))" />
</div>
<div class="sax-message"></div>
</div>
</div>
<div class="col-sm-4">
@* User Last Name *@
<div class="sax-form-control MemberLastName">
<div class="sax-label">
<label for="ifMemberLastName"><span>@Localiser["label.last.name"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
<input type="text" id="ifMemberLastName" name="MemberLastName" class="MemberLastName sax-input" autocomplete="off" maxlength="@(Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserAccount_LastName))" />
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
</div>
</div>
@* Member roles *@
<div class="sax-form-control MemberRoleCodes">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifMemberRoles"><span>@Localiser["label.roles"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
<input type="hidden" name="MemberRoleCodes" class="MemberRoleCodes sax-input" />
<div class="col"><textarea class="MemberRoles sax-input" rows="4" readonly="readonly"></textarea></div>
<div class="fixed"><button class="sax-button SelectMemberRoles" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Admin Comment *@
<div class="sax-form-control MemberAdminComment">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifMemberAdminComment"><span>@Localiser["label.comment"]</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<textarea type="text" id="ifMemberAdminComment" name="MemberAdminComment" class="MemberAdminComment sax-textarea" rows="6" autocomplete="off" maxlength="@(Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.UserOrganisation_AdminComment))"></textarea>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
}
</div>
@@ -0,0 +1,116 @@
@model Model_Organisation_Opportunities
@inject SaxLocaliser Localiser
@{
/* Organisation Opportunities - Opportunity dialog panel */
}
<script type="text/javascript">
sax.dlg.title.createOpportunity = '@Localiser.GetJavascriptText("organisation.caption.opportunity.create")';
sax.dlg.title.updateOpportunity = '@Localiser.GetJavascriptText("organisation.caption.opportunity.update")';
sax.dlg.title.selectRegion = '@Localiser.GetJavascriptText("reference.caption.region.select")';
sax.dlg.title.selectSport = '@Localiser.GetJavascriptText("reference.caption.sport.select")';
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOpportunityId", @class = "sax-form" }))
{
@Html.HiddenFor(model => model.OpportunityId, new { @class = "OpportunityId" })
}
<div id="dlgOpportunity" class="sax-dialog" title="">
<div class="spacer2"></div>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOpportunity", @class = "sax-form", @onSubmit = "return false;" }))
{
@Html.HiddenFor(model => model.OpportunityId, new { @id = "ifOpportunityId", @class = "OpportunityId" })
@Html.HiddenFor(model => model.OpportunityExists, new { @id = "ifOpportunityExists", @class = "OpportunityExists" })
<input type="hidden" id="ifOpportunityOrganisationId" class="OpportunityOrganisationId" name="OpportunityOrganisationId" value="@(Model.Filter.OrganisationId)" />
@* Opportunity type *@
<div class="sax-form-control OpportunityTypeCode">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="cbOpportunityType"><span>@Localiser["label.type"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
<select id="cbOpportunityType" class="OpportunityType sax-select" name="OpportunityTypeCode"></select>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Opportunity position *@
<div class="sax-form-control OpportunityPosition">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifOpportunityPosition"><span>@Localiser["label.position"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control">
@Html.TextBoxFor(model => model.OpportunityPosition, new { @id = "ifOpportunityPosition", @class = "OpportunityPosition sax-input", @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationOpportunity_Position) })
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Opportunity outline *@
<div class="sax-form-control OpportunityOutline">
<div class="sax-label">
<label for="ifOpportunityOutline"><span>@Localiser["label.outline"]</span><span class="required">&nbsp;*</span></label>
</div>
<div class="sax-control">
@Html.TextAreaFor(model => model.OpportunityOutline, new { @id = "ifOpportunityOutline", @class = "OpportunityOutline sax-textarea", @rows = 4, @maxlength = @Model.GetControlMaxLength(SaxDbDfn_Core.ColumnIdx.OrganisationOpportunity_Outline) })
</div>
<div class="sax-message"></div>
</div>
@* Sport *@
<div class="sax-form-control OpportunitySportId">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifOpportunitySportCaption"><span>@Localiser["label.sport"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.OpportunitySportId, new { @id = "ifOpportunitySportId", @class = "OpportunitySportId" })
<div class="col"><input type="text" id="ifOpportunitySportCaption" class="OpportunitySportCaption sax-input" readonly="readonly" /></div>
<div class="fixed"><button class="SelectOpportunitySport sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
@* Region *@
<div class="sax-form-control OpportunityRegionId">
<div class="row">
<div class="col-sm-3">
<div class="sax-label">
<label for="ifOpportunityRegionCaption"><span>@Localiser["label.region"]</span><span class="required">&nbsp;*</span></label>
</div>
</div>
<div class="col-sm">
<div class="sax-control row">
@Html.HiddenFor(model => model.OpportunityRegionId, new { @id = "ifOpportunityRegionId", @class = "OpportunityRegionId" })
<div class="col"><input type="text" id="ifOpportunityRegionCaption" class="OpportunityRegionCaption sax-input" readonly="readonly" /></div>
<div class="fixed"><button class="SelectOpportunityRegion sax-button" style="min-width:auto;margin-left:4px;padding:4px 6px">@Localiser["label.select"]</button></div>
</div>
<div class="sax-message"></div>
</div>
</div>
</div>
}
<div class="spacer2"></div>
</div>
<partial name="~/Views/Common/RegionSelect.cshtml" />
<partial name="~/Views/Common/SportSelect.cshtml" />
@@ -0,0 +1,9 @@
@model Model_Policies
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@Localiser["policies.caption.privacy.policy"]</h1>
<p>Use this page to detail your site's privacy policy.</p>
@@ -0,0 +1,169 @@
@model Model_Search_Organisations
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Search Organisations";
}
<div class="sax-panel Organisations sax-alpha">
<script type="text/javascript">
sax.lbl.view = '@(Localiser.GetJavascriptText("label.view"))';
sax.lbl.follow = '@(Localiser.GetJavascriptText("label.follow"))';
sax.dlg.title.organisation = '@(Localiser.GetJavascriptText("label.organisation"))';
</script>
<div class="row">
<div class="col-sm-5">
@* Filter Form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
@* Filter panel *@
<div class="sax-panel-filter mr-sm-2">
@* Filter header *@
<div class="sax-header row">
@* Filter caption *@
<div class="col"><div class="sax-caption">@Localiser["organisation.caption.organisations"]</div></div>
<div class="fixed row">
<div class="col">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="sax-content">
@* Generic Organisation filter - name etc. *@
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm row">
<div class="col"><input type="search" name="Filter.Organisation" class="sax-input FilterOrganisation" maxlength="32" autocomplete="off" /></div>
</div>
</div>
@* Organisation type *@
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.category"]</div></div>
<div class="col-sm">
@Html.DropDownListFor(model => model.Filter.Category, Model.OrganisationCategoriesForSelect, Localiser["label.any"], new { @id = "", @class = "FilterCategory sax-select" })
</div>
</div>
@* Organisation sport *@
<div class="row pb4">
<div class="col-sm-4 sax-label text-left text-sm-right">@Localiser["label.sport"]</div>
<div class="col-sm">
<div class="row">
@Html.HiddenFor(model => model.Filter.SportId, new { @id = "", @class = "FilterSportId" })
<div class="col"><input type="text" class="FilterSportCaption sax-input" readonly="readonly" ondblclick="sax.searchOrganisations.selectFilterSport()" /> </div>
<div class="fixed row">
<div class="sax-action" onclick="sax.searchOrganisations.selectFilterSport();"><img src="~/img/action/search24.png" alt="@(Localiser["label.search"])" title="@(Localiser["label.search"])" /></div>
<div class="sax-action" onclick="sax.searchOrganisations.clearFilterSport();"><img src="~/img/action/delete24.png" alt="@(Localiser["label.clear"])" title="@(Localiser["label.clear"])" /></div>
</div>
</div>
</div>
</div>
@* Organisation region *@
<div class="row pb4">
<div class="col-sm-4 sax-label text-left text-sm-right">@Localiser["label.region"]</div>
<div class="col-sm">
<div class="row">
@Html.HiddenFor(model => model.Filter.RegionId, new { @id = "", @class = "FilterRegionId" })
<div class="col"><input type="text" class="FilterRegionCaption sax-input" readonly="readonly" ondblclick="sax.searchOrganisations.selectFilterRegion()" /> </div>
<div class="fixed row">
<div class="sax-action" onclick="sax.searchOrganisations.selectFilterRegion();"><img src="~/img/action/search24.png" alt="@(Localiser["label.search"])" title="@(Localiser["label.search"])" /></div>
<div class="sax-action" onclick="sax.searchOrganisations.clearFilterRegion();"><img src="~/img/action/delete24.png" alt="@(Localiser["label.clear"])" title="@(Localiser["label.clear"])" /></div>
</div>
</div>
</div>
</div>
</div>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Organisations">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
}
</div>
<div class="col">
@* Results Panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
@* Element templates *@
<div class="sax-panel-item-template">
@* Item template *@
<div class="sax-panel-item Organisation">
<div class="row">
<div class="col">
<div class="sax-header row">
<div class="col">
@* Organisation caption *@
<div class="sax-caption"><div class="sax-action"><span class="OrganisationName"></span></div></div>
</div>
<div class="fixed">
<div class="OrganisationCategory"></div>
</div>
<div class="fixed sax-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="col">
<div class="row">
<div class="col-sm-6"><div class="OrganisationSport"></div></div>
<div class="col-sm-6 text-sm-right"><div class="OrganisationLocation"></div></div>
</div>
</div>
</div>
</div>
<div class="fixed"><div style="min-width:48px;"><img src="" class="OrganisationLogo" alt="" title=""></div></div>
</div>
</div>
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Organisation">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<partial name="~/Views/Common/RegionSelect.cshtml" />
<partial name="~/Views/Common/SportSelect.cshtml" />
<partial name="Panel-OrganisationDialog.cshtml" />
@@ -0,0 +1,99 @@
@model Model_Search_Organisations
@inject SaxLocaliser Localiser
@{
/* Organisation dialog panel */
}
<style>
#dlgOrganisation .OrganisationContact .sax-value img
{
margin-right: 8px;
}
#dlgOrganisation .OrganisationAssociations .sax-value, #dlgOrganisation .OrganisationAbout .sax-value, #dlgOrganisation .OrganisationFacilities .sax-value
{
min-height: 40px;
max-height: 170px;
padding: 4px 4px 2px 4px;
border: 1px solid #a0a0a0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
overflow-y: scroll;
}
#dlgOrganisation .OrganisationAssociations .sax-value .sax-assoc-type
{
padding: 2px 4px 2px 4px;
font-weight: bold;
background-color: #e0e0e0;
}
#dlgOrganisation .OrganisationAssociations .sax-value .sax-assoc-org
{
margin: 2px 0 0 12px;
padding: 0 0 0 8px;
}
#dlgOrganisation .OrganisationAssociations .sax-value .sax-assoc-org:hover
{
cursor: pointer;
text-decoration: underline;
}
#dlgOrganisation .OrganisationAssociations .sax-value .sax-assoc-org img.icon
{
height: 24px;
margin-right:4px;
}
</style>
<script type="text/javascript">
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmOrganisationId", @class = "sax-form" }))
{
<input type="hidden" name="OrganisationId" class="OrganisationId" />
}
<div id="dlgOrganisation" class="sax-dialog" title="">
<div class="spacer"><a href="#"></a>"</div>
<div class="row OrganisationCategory">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.type"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationSport">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.sport"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationLocation">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.location"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationAbout">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.about"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationFacilities">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.facilities"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationContact">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.contact"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationAddress">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.address"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationPostalCode">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.postal.code"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row OrganisationAssociations">
<div class="col-sm-3"><div class="sax-label text-sm-right">@Localiser["label.associations"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="spacer"></div>
</div>
@@ -0,0 +1,30 @@
@model Model_Search_Users
@inject SaxLocaliser Localiser
@{
/* User dialog panel */
}
<script type="text/javascript">
</script>
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserId", @class = "sax-form" }))
{
<input type="hidden" name="UserId" class="UserId" />
}
<div id="dlgUser" class="sax-dialog" title="">
<div class="spacer"></div>
<div class="row UserName">
<div class="col-sm-5"><div class="sax-label text-sm-right">@Localiser["label.name"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="row UserLocation">
<div class="col-sm-5"><div class="sax-label text-sm-right">@Localiser["label.location"]</div></div>
<div class="col-sm"><div class="sax-value"></div></div>
</div>
<div class="spacer"></div>
</div>
@@ -0,0 +1,154 @@
@model Model_Search_Users
@inject SaxLocaliser Localiser
@{
ViewData["Title"] = "Search Users";
}
<div class="sax-panel Users sax-alpha">
<script type="text/javascript">
sax.lbl.view = '@(Localiser.GetJavascriptText("label.view"))';
sax.lbl.follow = '@(Localiser.GetJavascriptText("label.follow"))';
sax.dlg.title.user = '@(Localiser.GetJavascriptText("label.user"))';
</script>
<div class="row">
<div class="col-sm-5">
@* Filter Form *@
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmUserFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
{
@* Filter panel *@
<div class="sax-panel-filter mr-sm-2">
@* Filter header *@
<div class="sax-header row">
@* Filter caption *@
<div class="col"><div class="sax-caption">@Localiser["label.users"]</div></div>
<div class="fixed row">
<div class="col">
<partial name="~/Views/Common/ResultsPaging.cshtml" />
</div>
<div class="fixed">
@* Burger menu *@
<ul class="nav navbar-nav navbar-dark navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
</div>
<div class="sax-content">
@* Generic User filter - name etc. *@
<div class="sax-control row">
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
<div class="col-sm row">
<div class="col"><input type="search" name="Filter.User" class="sax-input FilterUser" maxlength="32" autocomplete="off" /></div>
</div>
</div>
@* User sport *@
<div class="row pb4">
<div class="col-sm-4 sax-label text-left text-sm-right">@Localiser["label.sport"]</div>
<div class="col-sm">
<div class="row">
@Html.HiddenFor(model => model.Filter.SportId, new { @id = "", @class = "FilterSportId" })
<div class="col"><input type="text" class="FilterSportCaption sax-input" readonly="readonly" ondblclick="sax.searchUsers.selectFilterSport()" /> </div>
<div class="fixed row">
<div class="sax-action" onclick="sax.searchUsers.selectFilterSport();"><img src="~/img/action/search24.png" alt="@(Localiser["label.search"])" title="@(Localiser["label.search"])" /></div>
<div class="sax-action" onclick="sax.searchUsers.clearFilterSport();"><img src="~/img/action/delete24.png" alt="@(Localiser["label.clear"])" title="@(Localiser["label.clear"])" /></div>
</div>
</div>
</div>
</div>
@* User region *@
<div class="row pb4">
<div class="col-sm-4 sax-label text-left text-sm-right">@Localiser["label.region"]</div>
<div class="col-sm">
<div class="row">
@Html.HiddenFor(model => model.Filter.RegionId, new { @id = "", @class = "FilterRegionId" })
<div class="col"><input type="text" class="FilterRegionCaption sax-input" readonly="readonly" ondblclick="sax.searchUsers.selectFilterRegion()" /> </div>
<div class="fixed row">
<div class="sax-action" onclick="sax.searchUsers.selectFilterRegion();"><img src="~/img/action/search24.png" alt="@(Localiser["label.search"])" title="@(Localiser["label.search"])" /></div>
<div class="sax-action" onclick="sax.searchUsers.clearFilterRegion();"><img src="~/img/action/delete24.png" alt="@(Localiser["label.clear"])" title="@(Localiser["label.clear"])" /></div>
</div>
</div>
</div>
</div>
</div>
@* Filter item templates *@
<div class="sax-panel-item-template">
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu Users">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
}
</div>
<div class="col">
@* Results Panel *@
<div class="sax-panel-results">
<div class="sax-panel-messages"></div>
<div class="sax-panel-item-list"></div>
@* Element templates *@
<div class="sax-panel-item-template">
@* Item template *@
<div class="sax-panel-item User">
<div class="sax-header row">
<div class="col">
@* User caption *@
<div class="sax-caption"><div class="sax-action"><span class="UserName"></span></div></div>
</div>
<div class="fixed">
<div class="UserCategory"></div>
</div>
<div class="fixed sax-actions">
@* Burger menu *@
<ul class="nav navbar-nav navbar-light navbar-right sax-burger">
<li class="dropdown-toggle caret-off">
<a href="#" class="dropdown" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="navbar-toggler-icon"></span></a>
<ul class="dropdown-menu dropdown-menu-right sax-dropdown-menu">@* Menu items will be inserted here. *@</ul>
</li>
</ul>
</div>
</div>
<div class="sax-content">
<div class="row">
<div class="col-sm-6"><div class="UserLocation"></div></div>
<div class="col-sm-6 text-sm-right"><div class=""></div></div>
</div>
</div>
</div>
@* Burger dropdown item template - ul is ignored *@
<ul class="sax-dropdown-menu User">
<li class="sax-dropdown-item sax-action"><div class="sax-label">...</div></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<partial name="~/Views/Common/RegionSelect.cshtml" />
<partial name="~/Views/Common/SportSelect.cshtml" />
<partial name="Panel-UserDialog.cshtml" />

Some files were not shown because too many files have changed in this diff Show More