Private messages implemented

main
Inga 🏳‍🌈 15 years ago
parent 237e646c4d
commit c842f30f87
  1. 2
      Builder/IISMainHandler/build.txt
  2. 2
      Builder/IISUploadHandler/build.txt
  3. 3
      Common/Common.csproj
  4. 2
      Common/SqlObject.cs
  5. 3
      Common/UBBParser.cs
  6. 2
      Common/actions/ChangeSet.cs
  7. 14
      Common/actions/IncrementFieldValue.cs
  8. 7
      Common/actions/TwoWayReferenceFieldValue.cs
  9. 34
      Common/dataobjects/Account.cs
  10. 5
      Common/dataobjects/Board.cs
  11. 385
      Common/dataobjects/PMConversation.cs
  12. 199
      Common/dataobjects/PMMessage.cs
  13. 17
      Common/dataobjects/Thread.cs
  14. 12
      IISMainHandler/HandlersFactory.cs
  15. 6
      IISMainHandler/IISMainHandler.csproj
  16. 4
      IISMainHandler/handlers/AbstractGetHandler.cs
  17. 2
      IISMainHandler/handlers/BoardHandler.cs
  18. 2
      IISMainHandler/handlers/request/AbstractPostHandler.cs
  19. 40
      IISMainHandler/handlers/request/SendPMHandler.cs
  20. 2
      IISMainHandler/handlers/response/BoardAsThread.cs
  21. 80
      IISMainHandler/handlers/response/ConversationHandler.cs
  22. 37
      IISMainHandler/handlers/response/ConversationsHandler.cs
  23. 30
      IISMainHandler/handlers/response/PMReplyHandler.cs
  24. 33
      IISMainHandler/handlers/response/PMReplyToPostHandler.cs
  25. 26
      IISMainHandler/handlers/response/PMSendHandler.cs
  26. BIN
      static/images/newpm.gif
  27. 93
      templates/Full/Conversation.xslt
  28. 92
      templates/Full/Conversations.xslt
  29. 107
      templates/Full/PMReply.xslt
  30. 104
      templates/Full/PMReplyToPost.xslt
  31. 56
      templates/Full/PMSend.xslt
  32. 58
      templates/Full/elems/ConversationInfo.xslt
  33. 18
      templates/Full/elems/Header.xslt
  34. 105
      templates/Full/elems/PMInfo.xslt
  35. 3
      templates/Full/elems/PostInfo.xslt
  36. 30
      templates/Full/result/PMSent.xslt

@ -35,6 +35,7 @@
<Reference Include="System.Core"> <Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework> <RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference> </Reference>
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq"> <Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework> <RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference> </Reference>
@ -63,6 +64,8 @@
<Compile Include="dataobjects\Category.cs" /> <Compile Include="dataobjects\Category.cs" />
<Compile Include="dataobjects\IUserSettings.cs" /> <Compile Include="dataobjects\IUserSettings.cs" />
<Compile Include="dataobjects\AnonymousUserSettings.cs" /> <Compile Include="dataobjects\AnonymousUserSettings.cs" />
<Compile Include="dataobjects\PMConversation.cs" />
<Compile Include="dataobjects\PMMessage.cs" />
<Compile Include="dataobjects\Post.cs" /> <Compile Include="dataobjects\Post.cs" />
<Compile Include="dataobjects\PostLayer.cs" /> <Compile Include="dataobjects\PostLayer.cs" />
<Compile Include="dataobjects\Session.cs" /> <Compile Include="dataobjects\Session.cs" />

@ -53,7 +53,7 @@ namespace FLocal.Common {
this.fromHash(Config.instance.mainConnection.LoadById(this.table, this.id.ToString())); this.fromHash(Config.instance.mainConnection.LoadById(this.table, this.id.ToString()));
} }
protected void Load() { private void Load() {
lock(this.lockInitializer) { lock(this.lockInitializer) {
if(this.isLoaded) throw new CriticalException("already initialized"); if(this.isLoaded) throw new CriticalException("already initialized");
this.doLoad(); this.doLoad();

@ -2,12 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Web;
namespace FLocal.Common { namespace FLocal.Common {
public static class UBBParser { public static class UBBParser {
public static string UBBToIntermediate(string UBB) { public static string UBBToIntermediate(string UBB) {
return UBB; return HttpUtility.HtmlEncode(UBB).Replace("\r\n", "<br/>\r\n");
} }
public static string ShallerToUBB(string shaller) { public static string ShallerToUBB(string shaller) {

@ -27,6 +27,8 @@ namespace FLocal.Common.actions {
dataobjects.User.TableSpec.TABLE, dataobjects.User.TableSpec.TABLE,
dataobjects.AccountSettings.TableSpec.TABLE, dataobjects.AccountSettings.TableSpec.TABLE,
dataobjects.AccountIndicator.TableSpec.TABLE, dataobjects.AccountIndicator.TableSpec.TABLE,
dataobjects.PMConversation.TableSpec.TABLE,
dataobjects.PMMessage.TableSpec.TABLE,
dataobjects.Thread.ReadMarkerTableSpec.TABLE, dataobjects.Thread.ReadMarkerTableSpec.TABLE,
dataobjects.Board.ReadMarkerTableSpec.TABLE, dataobjects.Board.ReadMarkerTableSpec.TABLE,
dataobjects.Session.TableSpec.TABLE, dataobjects.Session.TableSpec.TABLE,

@ -6,21 +6,23 @@ using System.Text;
namespace FLocal.Common.actions { namespace FLocal.Common.actions {
class IncrementFieldValue : AbstractFieldValue { class IncrementFieldValue : AbstractFieldValue {
private readonly Func<string, string> incrementor; public static readonly Func<string, string> INCREMENTOR = s => (int.Parse(s)+1).ToString();
public static readonly Func<string, string> DECREMENTOR = s => (int.Parse(s)-1).ToString();
public IncrementFieldValue(Func<string, string> incrementor) { private readonly Func<string, string> processor;
this.incrementor = incrementor;
public IncrementFieldValue(Func<string, string> processor) {
this.processor = processor;
} }
public IncrementFieldValue() public IncrementFieldValue() : this(INCREMENTOR) {
: this(str => (int.Parse(str)+1).ToString()) {
} }
public override string getStringRepresentation() { public override string getStringRepresentation() {
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override string getStringRepresentation(string oldInfo) { public override string getStringRepresentation(string oldInfo) {
return this.incrementor(oldInfo); return this.processor(oldInfo);
} }
} }

@ -9,6 +9,13 @@ namespace FLocal.Common.actions {
public delegate string Calculator(string old, string reference); public delegate string Calculator(string old, string reference);
public static Calculator GREATEST = (old, reference) => {
if(old == null || old == "") {
return reference;
}
return Math.Max(int.Parse(old), int.Parse(reference)).ToString();
};
private Calculator calculator; private Calculator calculator;
public TwoWayReferenceFieldValue(AbstractChange referenced, Calculator calculator) public TwoWayReferenceFieldValue(AbstractChange referenced, Calculator calculator)

@ -61,6 +61,13 @@ namespace FLocal.Common.dataobjects {
this._needsMigration = Util.string2bool(data[TableSpec.FIELD_NEEDSMIGRATION]); this._needsMigration = Util.string2bool(data[TableSpec.FIELD_NEEDSMIGRATION]);
} }
public XElement exportToXml(UserContext context) {
return new XElement("account",
new XElement("id", this.id),
this.user.exportToXmlForViewing(context)
);
}
private static Dictionary<string, int> name2id = new Dictionary<string, int>(); private static Dictionary<string, int> name2id = new Dictionary<string, int>();
public static Account LoadByName(string _name) { public static Account LoadByName(string _name) {
string name = _name.ToLower(); string name = _name.ToLower();
@ -90,6 +97,33 @@ namespace FLocal.Common.dataobjects {
return Account.LoadById(name2id[name]); return Account.LoadById(name2id[name]);
} }
private static Dictionary<int, int> userid2id = new Dictionary<int, int>();
public static Account LoadByUser(User user) {
if(!userid2id.ContainsKey(user.id)) {
lock(userid2id) {
if(!userid2id.ContainsKey(user.id)) {
List<string> ids = Config.instance.mainConnection.LoadIdsByConditions(
TableSpec.instance,
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_USERID),
ComparisonType.EQUAL,
user.id.ToString()
),
Diapasone.unlimited
);
if(ids.Count > 1) {
throw new CriticalException("not unique");
} else if(ids.Count == 1) {
userid2id[user.id] = int.Parse(ids[0]);
} else {
throw new NotFoundInDBException();
}
}
}
}
return Account.LoadById(userid2id[user.id]);
}
private string hashPassword(string password) { private string hashPassword(string password) {
return Util.md5(password + " " + Config.instance.SaltMigration + " " + this.id); return Util.md5(password + " " + Config.instance.SaltMigration + " " + this.id);
} }

@ -216,7 +216,7 @@ namespace FLocal.Common.dataobjects {
return result; return result;
} }
public IEnumerable<Thread> getThreads(Diapasone diapasone, UserContext context, SortSpec[] sortBy) { public IEnumerable<Thread> getThreads(Diapasone diapasone, SortSpec[] sortBy) {
return Thread.LoadByIds( return Thread.LoadByIds(
from stringId in Config.instance.mainConnection.LoadIdsByConditions( from stringId in Config.instance.mainConnection.LoadIdsByConditions(
Thread.TableSpec.instance, Thread.TableSpec.instance,
@ -232,10 +232,9 @@ namespace FLocal.Common.dataobjects {
); );
} }
public IEnumerable<Thread> getThreads(Diapasone diapasone, UserContext context) { public IEnumerable<Thread> getThreads(Diapasone diapasone) {
return this.getThreads( return this.getThreads(
diapasone, diapasone,
context,
new SortSpec[] { new SortSpec[] {
new SortSpec( new SortSpec(
Thread.TableSpec.instance.getColumnSpec(Thread.TableSpec.FIELD_ISANNOUNCEMENT), Thread.TableSpec.instance.getColumnSpec(Thread.TableSpec.FIELD_ISANNOUNCEMENT),

@ -0,0 +1,385 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using FLocal.Core;
using FLocal.Core.DB;
using FLocal.Core.DB.conditions;
using FLocal.Common.actions;
namespace FLocal.Common.dataobjects {
public class PMConversation : SqlObject<PMConversation> {
public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "PMConversations";
public const string FIELD_ID = "Id";
public const string FIELD_OWNERID = "OwnerAccountId";
public const string FIELD_INTERLOCUTORID = "InterlocutorAccountId";
public const string FIELD_TOTALMESSAGES = "TotalMessages";
public const string FIELD_LASTMESSAGEID = "LastMessageId";
public const string FIELD_LASTMESSAGEDATE = "LastMessageDate";
public const string FIELD_LASTREADMESSAGEID = "LastReadMessageId";
public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
}
protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private int _ownerId;
public int ownerId {
get {
this.LoadIfNotLoaded();
return this._ownerId;
}
}
public Account owner {
get {
return Account.LoadById(this.ownerId);
}
}
private int _interlocutorId;
public int interlocutorId {
get {
this.LoadIfNotLoaded();
return this._interlocutorId;
}
}
public Account interlocutor {
get {
return Account.LoadById(this.interlocutorId);
}
}
private int _totalMessages;
public int totalMessages {
get {
this.LoadIfNotLoaded();
return this._totalMessages;
}
}
private int _lastMessageId;
public int lastMessageId {
get {
this.LoadIfNotLoaded();
return this._lastMessageId;
}
}
public PMMessage lastMessage {
get {
return PMMessage.LoadById(this.lastMessageId);
}
}
private DateTime _lastMessageDate;
public DateTime lastMessageDate {
get {
this.LoadIfNotLoaded();
return this._lastMessageDate;
}
}
private int? _lastReadMessageId;
public int? lastReadMessageId {
get {
this.LoadIfNotLoaded();
return this._lastReadMessageId;
}
}
public PMMessage lastReadMessage {
get {
return PMMessage.LoadById(this.lastReadMessageId.Value);
}
}
protected override void doFromHash(Dictionary<string, string> data) {
this._ownerId = int.Parse(data[TableSpec.FIELD_OWNERID]);
this._interlocutorId = int.Parse(data[TableSpec.FIELD_INTERLOCUTORID]);
this._totalMessages = int.Parse(data[TableSpec.FIELD_TOTALMESSAGES]);
this._lastMessageId = int.Parse(data[TableSpec.FIELD_LASTMESSAGEID]);
this._lastMessageDate = new DateTime(long.Parse(data[TableSpec.FIELD_LASTMESSAGEDATE]));
this._lastReadMessageId = Util.ParseInt(data[TableSpec.FIELD_LASTREADMESSAGEID]);
}
public XElement exportToXmlSimpleWithParent(UserContext context) {
return new XElement("conversation",
new XElement("id", this.id),
new XElement("name", this.interlocutor.user.name)
);
}
public XElement exportToXml(UserContext context, bool includeFirstPost, params XElement[] additional) {
if((context.account == null) || (context.account.id != this.owner.id)) throw new AccessViolationException();
XElement result = new XElement("conversation",
new XElement("id", this.id),
new XElement("owner", this.owner.exportToXml(context)),
new XElement("interlocutor", this.interlocutor.exportToXml(context)),
new XElement("totalMessages", this.totalMessages),
new XElement("lastMessageId", this.lastMessageId),
new XElement("lastMessageDate", this.lastMessageDate.ToXml()),
new XElement("lastReadMessageId", this.lastReadMessageId),
new XElement("afterLastRead", this.lastReadMessageId + 1),
new XElement(
"totalNewMessages",
Config.instance.mainConnection.GetCountByConditions(
PMMessage.TableSpec.instance,
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
this.ownerId.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
this.interlocutorId.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getIdSpec(),
ComparisonType.GREATERTHAN,
(this.lastReadMessageId.HasValue ? this.lastReadMessageId.Value : 0).ToString()
)
)
)
),
context.formatTotalPosts(this.totalMessages)
);
if(additional.Length > 0) {
result.Add(additional);
}
return result;
}
public IEnumerable<PMMessage> getMessages(Diapasone diapasone, UserContext context) {
return PMMessage.LoadByIds(
from stringId in Config.instance.mainConnection.LoadIdsByConditions(
PMMessage.TableSpec.instance,
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
this.ownerId.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
this.interlocutorId.ToString()
)
),
diapasone,
new JoinSpec[0],
new SortSpec[] {
new SortSpec(
PMMessage.TableSpec.instance.getIdSpec(),
true
),
}
) select int.Parse(stringId)
);
}
public void markAsRead(Account account, PMMessage minMessage, PMMessage maxMessage) {
if(this.ownerId != account.id) throw new AccessViolationException();
ChangeSetUtil.ApplyChanges(new AbstractChange[] {
new UpdateChange(
TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{
TableSpec.FIELD_LASTREADMESSAGEID,
new IncrementFieldValue(
s => {
if((s == null) || (s == "")) {
s = "0"; //workaround
}
if(maxMessage.id < int.Parse(s)) {
return (s == "0") ? null : s; //if some newer posts were already read
}
long count = Config.instance.mainConnection.GetCountByConditions(
PMMessage.TableSpec.instance,
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
this.ownerId.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
this.interlocutorId.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getIdSpec(),
ComparisonType.GREATERTHAN,
s
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getIdSpec(),
ComparisonType.LESSTHAN,
minMessage.id.ToString()
)
)
);
if(count > 0) {
return (s == "0") ? null : s; //if there are some unread posts earlier than minPost
} else {
return maxMessage.id.ToString();
}
}
)
}
},
this.id
)
});
}
public static IEnumerable<PMConversation> getConversations(Account owner, Diapasone diapasone) {
return LoadByIds(
from stringId in Config.instance.mainConnection.LoadIdsByConditions(
TableSpec.instance,
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
owner.id.ToString()
),
diapasone
) select int.Parse(stringId)
);
}
public static PMConversation LoadByAccounts(Account owner, Account interlocutor) {
return LoadById(
int.Parse(Config.instance.mainConnection.LoadIdsByConditions(
TableSpec.instance,
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
owner.id.ToString()
),
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
interlocutor.id.ToString()
)
),
Diapasone.unlimited
).Single())
);
}
public static PMMessage SendPMMessage(Account sender, Account receiver, string title, string bodyUBB) {
string bodyIntermediate = UBBParser.UBBToIntermediate(bodyUBB);
AbstractChange insertPmReceiver = new InsertChange(
PMMessage.TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ PMMessage.TableSpec.FIELD_OWNERID, new ScalarFieldValue(receiver.id.ToString()) },
{ PMMessage.TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(sender.id.ToString()) },
{ PMMessage.TableSpec.FIELD_DIRECTION, new ScalarFieldValue(PMMessage.ENUM_DIRECTION_INCOMING) },
{ PMMessage.TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
{ PMMessage.TableSpec.FIELD_TITLE, new ScalarFieldValue(title) },
{ PMMessage.TableSpec.FIELD_BODY, new ScalarFieldValue(bodyIntermediate) },
{ PMMessage.TableSpec.FIELD_BODYUBB, new ScalarFieldValue(bodyUBB) },
{ PMMessage.TableSpec.FIELD_INCOMINGPMID, new ScalarFieldValue(null) },
{ PMMessage.TableSpec.FIELD_ISREAD, new ScalarFieldValue("0") },
}
);
AbstractChange insertPmSender = new InsertChange(
PMMessage.TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ PMMessage.TableSpec.FIELD_OWNERID, new ScalarFieldValue(sender.id.ToString()) },
{ PMMessage.TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(receiver.id.ToString()) },
{ PMMessage.TableSpec.FIELD_DIRECTION, new ScalarFieldValue(PMMessage.ENUM_DIRECTION_OUTGOING) },
{ PMMessage.TableSpec.FIELD_POSTDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
{ PMMessage.TableSpec.FIELD_TITLE, new ScalarFieldValue(title) },
{ PMMessage.TableSpec.FIELD_BODY, new ScalarFieldValue(bodyIntermediate) },
{ PMMessage.TableSpec.FIELD_BODYUBB, new ScalarFieldValue(bodyUBB) },
{ PMMessage.TableSpec.FIELD_INCOMINGPMID, new ReferenceFieldValue(insertPmReceiver) },
{ PMMessage.TableSpec.FIELD_ISREAD, new ScalarFieldValue("1") },
}
);
AbstractChange updateConversationSender = new InsertOrUpdateChange(
TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ TableSpec.FIELD_OWNERID, new ScalarFieldValue(sender.id.ToString()) },
{ TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(receiver.id.ToString()) },
{ TableSpec.FIELD_TOTALMESSAGES, new ScalarFieldValue("1") },
{ TableSpec.FIELD_LASTMESSAGEID, new ReferenceFieldValue(insertPmSender) },
{ TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
{ TableSpec.FIELD_LASTREADMESSAGEID, new ReferenceFieldValue(insertPmSender) },
},
new Dictionary<string,AbstractFieldValue> {
{ TableSpec.FIELD_TOTALMESSAGES, new IncrementFieldValue() },
{ TableSpec.FIELD_LASTMESSAGEID, new TwoWayReferenceFieldValue(insertPmSender, TwoWayReferenceFieldValue.GREATEST) },
},
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
sender.id.ToString()
),
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
receiver.id.ToString()
)
)
);
AbstractChange updateConversationReceiver = new InsertOrUpdateChange(
TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ TableSpec.FIELD_OWNERID, new ScalarFieldValue(receiver.id.ToString()) },
{ TableSpec.FIELD_INTERLOCUTORID, new ScalarFieldValue(sender.id.ToString()) },
{ TableSpec.FIELD_TOTALMESSAGES, new ScalarFieldValue("1") },
{ TableSpec.FIELD_LASTMESSAGEID, new ReferenceFieldValue(insertPmReceiver) },
{ TableSpec.FIELD_LASTMESSAGEDATE, new ScalarFieldValue(DateTime.Now.ToUTCString()) },
{ TableSpec.FIELD_LASTREADMESSAGEID, new ScalarFieldValue(null) },
},
new Dictionary<string,AbstractFieldValue> {
{ TableSpec.FIELD_TOTALMESSAGES, new IncrementFieldValue() },
{ TableSpec.FIELD_LASTMESSAGEID, new TwoWayReferenceFieldValue(insertPmReceiver, TwoWayReferenceFieldValue.GREATEST) },
},
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
receiver.id.ToString()
),
new ComparisonCondition(
TableSpec.instance.getColumnSpec(TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
sender.id.ToString()
)
)
);
AbstractChange updateIndicatorReceiver = new UpdateChange(
AccountIndicator.TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ AccountIndicator.TableSpec.FIELD_PRIVATEMESSAGES, new IncrementFieldValue() },
{ AccountIndicator.TableSpec.FIELD_UNREADPRIVATEMESSAGES, new IncrementFieldValue() },
},
AccountIndicator.LoadByAccount(receiver).id
);
ChangeSetUtil.ApplyChanges(
insertPmReceiver,
insertPmSender,
updateConversationReceiver,
updateConversationSender,
updateIndicatorReceiver
);
return PMMessage.LoadById(insertPmSender.getId().Value);
}
}
}

@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using FLocal.Core;
using FLocal.Core.DB;
using FLocal.Common;
using FLocal.Common.actions;
namespace FLocal.Common.dataobjects {
public class PMMessage : SqlObject<PMMessage> {
public const string ENUM_DIRECTION_INCOMING = "Incoming";
public const string ENUM_DIRECTION_OUTGOING = "Outgoing";
public class TableSpec : ISqlObjectTableSpec {
public const string TABLE = "PMMessages";
public const string FIELD_ID = "Id";
public const string FIELD_OWNERID = "OwnerId";
public const string FIELD_INTERLOCUTORID = "InterlocutorId";
public const string FIELD_DIRECTION = "Direction";
public const string FIELD_POSTDATE = "PostDate";
public const string FIELD_TITLE = "Title";
public const string FIELD_BODY = "Body";
public const string FIELD_BODYUBB = "BodyUBB";
public const string FIELD_INCOMINGPMID = "IncomingPMId";
public const string FIELD_ISREAD = "IsRead";
public static readonly TableSpec instance = new TableSpec();
public string name { get { return TABLE; } }
public string idName { get { return FIELD_ID; } }
public void refreshSqlObject(int id) { Refresh(id); }
}
protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } }
private int _ownerId;
public int ownerId {
get {
this.LoadIfNotLoaded();
return this._ownerId;
}
}
public Account owner {
get {
return Account.LoadById(this.ownerId);
}
}
private int _interlocutorId;
public int interlocutorId {
get {
this.LoadIfNotLoaded();
return this._interlocutorId;
}
}
public Account interlocutor {
get {
return Account.LoadById(this.interlocutorId);
}
}
public Account poster {
get {
if(this.direction == ENUM_DIRECTION_INCOMING) {
return this.interlocutor;
} else {
return this.owner;
}
}
}
private string _direction;
public string direction {
get {
this.LoadIfNotLoaded();
return this._direction;
}
}
private DateTime _postDate;
public DateTime postDate {
get {
this.LoadIfNotLoaded();
return this._postDate;
}
}
private string _title;
public string title {
get {
this.LoadIfNotLoaded();
return this._title;
}
}
private string _body;
public string body {
get {
this.LoadIfNotLoaded();
return this._body;
}
}
private string _bodyUBB;
public string bodyUBB {
get {
this.LoadIfNotLoaded();
return this._bodyUBB;
}
}
private int? _incomingPMId;
public int? incomingPMId {
get {
this.LoadIfNotLoaded();
return this._incomingPMId;
}
}
private bool _isRead;
public bool isRead {
get {
this.LoadIfNotLoaded();
return this._isRead;
}
}
public PMConversation conversation {
get {
return PMConversation.LoadByAccounts(this.owner, this.interlocutor);
}
}
protected override void doFromHash(Dictionary<string, string> data) {
this._ownerId = int.Parse(data[TableSpec.FIELD_OWNERID]);
this._interlocutorId = int.Parse(data[TableSpec.FIELD_INTERLOCUTORID]);
this._direction = data[TableSpec.FIELD_DIRECTION];
this._postDate = Util.ParseDateTimeFromTimestamp(data[TableSpec.FIELD_POSTDATE]).Value;
this._title = data[TableSpec.FIELD_TITLE];
this._body = data[TableSpec.FIELD_BODY];
this._bodyUBB = data[TableSpec.FIELD_BODYUBB];
this._incomingPMId = Util.ParseInt(data[TableSpec.FIELD_INCOMINGPMID]);
this._isRead = Util.string2bool(data[TableSpec.FIELD_ISREAD]);
}
public XElement exportToXml(UserContext context, params XElement[] additional) {
if((context.account == null) || (context.account.id != this.owner.id)) {
throw new AccessViolationException();
}
XElement result = new XElement("message",
new XElement("id", this.id),
new XElement("poster", this.poster.exportToXml(context)),
new XElement("owner", this.owner.exportToXml(context)),
new XElement("interlocutor", this.interlocutor.exportToXml(context)),
new XElement("postDate", this.postDate.ToXml()),
new XElement("title", this.title),
new XElement("body", context.outputParams.preprocessBodyIntermediate(this.body)),
new XElement("bodyUBB", this.bodyUBB)
);
if(additional.Length > 0) {
result.Add(additional);
}
return result;
}
private readonly object MarkAsRead_locker = new object();
public void MarkAsRead(Account account) {
if(account.id != this.owner.id) throw new AccessViolationException();
if(!this.isRead) {
lock(MarkAsRead_locker) {
//so we can safely decrease ReadPrivateMessages counter
//Note that this code implicitly uses assumption of single-instance Common.dll; race condition is possible with more than one server
if(!this.isRead) {
AccountIndicator indicator = AccountIndicator.LoadByAccount(this.owner);
ChangeSetUtil.ApplyChanges(
new UpdateChange(
TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ TableSpec.FIELD_ISREAD, new ScalarFieldValue("1") },
},
this.id
),
new UpdateChange(
AccountIndicator.TableSpec.instance,
new Dictionary<string,AbstractFieldValue> {
{ AccountIndicator.TableSpec.FIELD_UNREADPRIVATEMESSAGES, new IncrementFieldValue(IncrementFieldValue.DECREMENTOR) },
},
indicator.id
)
);
}
}
}
}
}
}

@ -98,6 +98,11 @@ namespace FLocal.Common.dataobjects {
return this._lastPostId; return this._lastPostId;
} }
} }
public Post lastPost {
get {
return Post.LoadById(this.lastPostId);
}
}
private DateTime _lastPostDate; private DateTime _lastPostDate;
public DateTime lastPostDate { public DateTime lastPostDate {
@ -392,17 +397,7 @@ namespace FLocal.Common.dataobjects {
{ Thread.TableSpec.FIELD_TOTALPOSTS, new IncrementFieldValue() }, { Thread.TableSpec.FIELD_TOTALPOSTS, new IncrementFieldValue() },
{ {
Thread.TableSpec.FIELD_LASTPOSTID, Thread.TableSpec.FIELD_LASTPOSTID,
new TwoWayReferenceFieldValue( new TwoWayReferenceFieldValue(postInsert, TwoWayReferenceFieldValue.GREATEST)
postInsert,
(oldStringId, newStringId) => {
if((oldStringId == null) || (oldStringId == "")) {
return newStringId;
}
int oldId = int.Parse(oldStringId);
int newId = int.Parse(newStringId);
return Math.Max(oldId, newId).ToString();
}
)
} }
}; };
if(isNewThread) { if(isNewThread) {

@ -51,6 +51,8 @@ namespace FLocal.IISHandler {
switch(context.requestParts[2].ToLower()) { switch(context.requestParts[2].ToLower()) {
case "reply": case "reply":
return new handlers.response.ReplyHandler(); return new handlers.response.ReplyHandler();
case "pmreply":
return new handlers.response.PMReplyToPostHandler();
default: default:
return new handlers.WrongUrlHandler(); return new handlers.WrongUrlHandler();
} }
@ -64,6 +66,14 @@ namespace FLocal.IISHandler {
return new handlers.response.UserInfoHandler(); return new handlers.response.UserInfoHandler();
case "settings": case "settings":
return new handlers.response.SettingsHandler(); return new handlers.response.SettingsHandler();
case "conversations":
return new handlers.response.ConversationsHandler();
case "conversation":
return new handlers.response.ConversationHandler();
case "pmsend":
return new handlers.response.PMSendHandler();
case "pmreply":
return new handlers.response.PMReplyHandler();
case "upload": case "upload":
if(context.requestParts.Length < 2) { if(context.requestParts.Length < 2) {
return new handlers.WrongUrlHandler(); return new handlers.WrongUrlHandler();
@ -97,6 +107,8 @@ namespace FLocal.IISHandler {
return new handlers.request.CreateThreadHandler(); return new handlers.request.CreateThreadHandler();
case "settings": case "settings":
return new handlers.request.SettingsHandler(); return new handlers.request.SettingsHandler();
case "sendpm":
return new handlers.request.SendPMHandler();
case "upload": case "upload":
return new handlers.request.UploadHandler(); return new handlers.request.UploadHandler();
default: default:

@ -65,13 +65,19 @@
<Compile Include="handlers\request\LogoutHandler.cs" /> <Compile Include="handlers\request\LogoutHandler.cs" />
<Compile Include="handlers\request\MigrateAccountHandler.cs" /> <Compile Include="handlers\request\MigrateAccountHandler.cs" />
<Compile Include="handlers\request\ReplyHandler.cs" /> <Compile Include="handlers\request\ReplyHandler.cs" />
<Compile Include="handlers\request\SendPMHandler.cs" />
<Compile Include="handlers\request\SettingsHandler.cs" /> <Compile Include="handlers\request\SettingsHandler.cs" />
<Compile Include="handlers\request\UploadHandler.cs" /> <Compile Include="handlers\request\UploadHandler.cs" />
<Compile Include="handlers\response\BoardAsThread.cs" /> <Compile Include="handlers\response\BoardAsThread.cs" />
<Compile Include="handlers\response\ConversationHandler.cs" />
<Compile Include="handlers\response\ConversationsHandler.cs" />
<Compile Include="handlers\response\CreateThreadHandler.cs" /> <Compile Include="handlers\response\CreateThreadHandler.cs" />
<Compile Include="handlers\response\LegacyUploadHandler.cs" /> <Compile Include="handlers\response\LegacyUploadHandler.cs" />
<Compile Include="handlers\response\LoginHandler.cs" /> <Compile Include="handlers\response\LoginHandler.cs" />
<Compile Include="handlers\response\MigrateAccountHandler.cs" /> <Compile Include="handlers\response\MigrateAccountHandler.cs" />
<Compile Include="handlers\response\PMReplyHandler.cs" />
<Compile Include="handlers\response\PMReplyToPostHandler.cs" />
<Compile Include="handlers\response\PMSendHandler.cs" />
<Compile Include="handlers\response\ReplyHandler.cs" /> <Compile Include="handlers\response\ReplyHandler.cs" />
<Compile Include="handlers\response\SettingsHandler.cs" /> <Compile Include="handlers\response\SettingsHandler.cs" />
<Compile Include="handlers\response\UploadHandler.cs" /> <Compile Include="handlers\response\UploadHandler.cs" />

@ -17,10 +17,10 @@ namespace FLocal.IISHandler.handlers {
private XDocument getData(WebContext context) { private XDocument getData(WebContext context) {
return new XDocument( return new XDocument(
new XElement("root", new XElement("root",
this.getSpecificData(context),
new XElement("title", Config.instance.AppInfo), new XElement("title", Config.instance.AppInfo),
context.exportSession(), context.exportSession(),
context.userSettings.skin.exportToXml(), context.userSettings.skin.exportToXml()
this.getSpecificData(context)
) )
); );
} }

@ -22,7 +22,7 @@ namespace FLocal.IISHandler.handlers {
override protected XElement[] getSpecificData(WebContext context) { override protected XElement[] getSpecificData(WebContext context) {
Board board = Board.LoadById(int.Parse(context.requestParts[1])); Board board = Board.LoadById(int.Parse(context.requestParts[1]));
PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 2); PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 2);
IEnumerable<Thread> threads = board.getThreads(pageOuter, context); IEnumerable<Thread> threads = board.getThreads(pageOuter);
XElement[] result = new XElement[] { XElement[] result = new XElement[] {
new XElement("currentLocation", board.exportToXmlSimpleWithParent(context)), new XElement("currentLocation", board.exportToXmlSimpleWithParent(context)),
new XElement("boards", from subBoard in board.subBoards select subBoard.exportToXml(context, true)), new XElement("boards", from subBoard in board.subBoards select subBoard.exportToXml(context, true)),

@ -30,8 +30,8 @@ namespace FLocal.IISHandler.handlers.request {
private XDocument getData(WebContext context) { private XDocument getData(WebContext context) {
return new XDocument( return new XDocument(
new XElement("root", new XElement("root",
new XElement("title", Config.instance.AppInfo),
this.Do(context), this.Do(context),
new XElement("title", Config.instance.AppInfo),
context.userSettings.skin.exportToXml(), context.userSettings.skin.exportToXml(),
context.exportSession() context.exportSession()
) )

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using FLocal.Common.dataobjects;
namespace FLocal.IISHandler.handlers.request {
class SendPMHandler : AbstractNewMessageHandler {
protected override string templateName {
get {
return "result/PMSent.xslt";
}
}
protected override XElement[] Do(WebContext context) {
Account receiver;
if(context.httprequest.Form.AllKeys.Contains("receiverId")) {
receiver = Account.LoadById(int.Parse(context.httprequest.Form["receiverId"]));
} else if(context.httprequest.Form.AllKeys.Contains("receiverName")) {
receiver = Account.LoadByUser(User.LoadByName(context.httprequest.Form["receiverName"]));
} else {
throw new ApplicationException("receiverId/receiverName not passed");
}
PMMessage newMessage = PMConversation.SendPMMessage(
context.account,
receiver,
this.getTitle(context),
this.getBody(context)
);
newMessage.conversation.markAsRead(context.session.account, newMessage, newMessage);
return new XElement[] { newMessage.exportToXml(context) };
}
}
}

@ -23,7 +23,7 @@ namespace FLocal.IISHandler.handlers.response {
PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.postsPerPage, 2); PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.postsPerPage, 2);
IEnumerable<Thread> threads = board.getThreads( IEnumerable<Thread> threads = board.getThreads(
pageOuter, pageOuter,
context, new SortSpec[] { new SortSpec[] {
new SortSpec( new SortSpec(
Thread.TableSpec.instance.getColumnSpec(Thread.TableSpec.FIELD_ID), Thread.TableSpec.instance.getColumnSpec(Thread.TableSpec.FIELD_ID),
true true

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using FLocal.Common;
using FLocal.Common.dataobjects;
using FLocal.Core;
using FLocal.Core.DB;
using FLocal.Core.DB.conditions;
namespace FLocal.IISHandler.handlers.response {
class ConversationHandler : AbstractGetHandler {
override protected string templateName {
get {
return "Conversation.xslt";
}
}
override protected XElement[] getSpecificData(WebContext context) {
Account interlocutor = Account.LoadById(int.Parse(context.requestParts[1]));
PMConversation conversation = PMConversation.LoadByAccounts(context.session.account, interlocutor);
PageOuter pageOuter = PageOuter.createFromGet(
context.requestParts,
context.userSettings.postsPerPage,
new Dictionary<char,Func<long>> {
{
'p',
() => Config.instance.mainConnection.GetCountByConditions(
PMMessage.TableSpec.instance,
new ComplexCondition(
ConditionsJoinType.AND,
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_OWNERID),
ComparisonType.EQUAL,
context.session.account.id.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getColumnSpec(PMMessage.TableSpec.FIELD_INTERLOCUTORID),
ComparisonType.EQUAL,
interlocutor.id.ToString()
),
new ComparisonCondition(
PMMessage.TableSpec.instance.getIdSpec(),
ComparisonType.LESSTHAN,
int.Parse(context.requestParts[2].PHPSubstring(1)).ToString()
)
)
)
}
},
2
);
IEnumerable<PMMessage> messages = conversation.getMessages(pageOuter, context);
XElement[] result = new XElement[] {
conversation.exportToXml(context, false),
new XElement("messages",
from message in messages select message.exportToXml(context),
pageOuter.exportToXml(2, 5, 2)
)
};
if(messages.Count() > 0) {
conversation.markAsRead(context.session.account, messages.Min(), messages.Max());
}
foreach(var message in messages) {
message.MarkAsRead(context.session.account);
}
return result;
}
}
}

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using FLocal.Common;
using FLocal.Common.dataobjects;
using FLocal.Core;
using FLocal.Core.DB;
namespace FLocal.IISHandler.handlers.response {
class ConversationsHandler : AbstractGetHandler {
override protected string templateName {
get {
return "Conversations.xslt";
}
}
override protected XElement[] getSpecificData(WebContext context) {
PageOuter pageOuter = PageOuter.createFromGet(context.requestParts, context.userSettings.threadsPerPage, 1);
IEnumerable<PMConversation> conversations = PMConversation.getConversations(context.session.account, pageOuter);
XElement[] result = new XElement[] {
new XElement("conversations",
from conversation in conversations select conversation.exportToXml(context, false),
pageOuter.exportToXml(1, 5, 1)
)
};
return result;
}
}
}

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using FLocal.Core;
using FLocal.Common;
using FLocal.Common.dataobjects;
namespace FLocal.IISHandler.handlers.response {
class PMReplyHandler : AbstractGetHandler {
override protected string templateName {
get {
return "PMReply.xslt";
}
}
override protected XElement[] getSpecificData(WebContext context) {
PMMessage message = PMMessage.LoadById(int.Parse(context.requestParts[1]));
return new XElement[] {
message.exportToXml(context)
};
}
}
}

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using FLocal.Core;
using FLocal.Common;
using FLocal.Common.dataobjects;
namespace FLocal.IISHandler.handlers.response {
class PMReplyToPostHandler : AbstractGetHandler {
override protected string templateName {
get {
return "PMReplyToPost.xslt";
}
}
override protected XElement[] getSpecificData(WebContext context) {
Post post = Post.LoadById(int.Parse(context.requestParts[1]));
return new XElement[] {
post.thread.board.exportToXml(context, false),
post.thread.exportToXml(context, false),
post.exportToXmlWithoutThread(context, false),
new XElement("receiver", Account.LoadByUser(post.poster).exportToXml(context)),
};
}
}
}

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using FLocal.Core;
using FLocal.Common;
using FLocal.Common.dataobjects;
namespace FLocal.IISHandler.handlers.response {
class PMSendHandler : AbstractGetHandler {
override protected string templateName {
get {
return "PMSend.xslt";
}
}
override protected XElement[] getSpecificData(WebContext context) {
return new XElement[0];
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="elems\Main.xslt"/>
<xsl:import href="elems\PMInfo.xslt"/>
<xsl:template name="specific">
<xsl:call-template name="conversationInfo"/>
<br />
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="0" cellspacing="1" width="100%" class="tableborders">
<tr class="tdheader">
<td>
<table width="100%" cellspacing="1" cellpadding="3" border="0">
<tr>
<td>
<xsl:text>ñòðàíèöû:</xsl:text>
<xsl:apply-templates select="messages/pageOuter" mode="withCurrent">
<xsl:with-param name="baseLink">/Conversation/<xsl:value-of select="conversation/interlocutor/account/id"/>/</xsl:with-param>
</xsl:apply-templates>
</td>
</tr>
</table>
</td>
</tr>
<xsl:apply-templates select="messages/message"/>
<tr class="tdheader">
<td>
<table width="100%" cellspacing="1" cellpadding="3" border="0">
<tr>
<td>
<xsl:text>ñòðàíèöû:</xsl:text>
<xsl:apply-templates select="messages/pageOuter" mode="withCurrent">
<xsl:with-param name="baseLink">/Conversation/<xsl:value-of select="conversation/interlocutor/account/id"/>/</xsl:with-param>
</xsl:apply-templates>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br />
<xsl:call-template name="conversationInfo"/>
</xsl:template>
<xsl:template name="conversationInfo">
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr class="darktable">
<td>
<table width="100%" cellpadding="0" cellspacing="0">
<tr class="darktable">
<td align="left" width="33%">
<font class="catandforum">
<a href="/Conversations"><xsl:text>Ïðèâàòíûå ñîîáùåíèÿ</xsl:text></a>
<xsl:text> &gt;&gt; </xsl:text>
<xsl:value-of select="conversation/interlocutor/account/user/name"/>
</font>
</td>
<td width="33%" align="right">
<table border="0" class="tablesurround">
<tr>
<td class="navigation" nowrap="nowrap">
<a>
<img alt="*" src="/static/images/greyflat.gif" style="vertical-align: text-bottom" />
<xsl:text>Ïëîñêèé</xsl:text>
</a>
</td>
<td class="navigation" nowrap="nowrap">
<a>
<img src="/static/images/greythreaded.gif" border="0" style="vertical-align: text-bottom" alt="Äåðåâî" />
<xsl:text>Äåðåâî</xsl:text>
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="elems\Main.xslt"/>
<xsl:import href="elems\ConversationInfo.xslt"/>
<xsl:template name="specific">
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" border="0" class="tableborders">
<tr class="darktable">
<td colspan="6">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td colspan="2" class="catandforum">
<xsl:text>Ïðèâàòíûå ñîîáùåíèÿ</xsl:text>
</td>
<td align="right" valign="bottom">
<table border="0" class="tablesurround">
<tr>
<td class="navigation" nowrap="nowrap">
<!-- postoption is either newpost.gif or greynewpost.gif -->
<a>
<xsl:attribute name="href">/PMSend/</xsl:attribute>
<img src="/static/images/newpost.gif" alt="Íîâîå ñîîáùåíèå" border="0" width="13" height="15" style="vertical-align: text-bottom" />
<xsl:text>Ñîîáùåíèå</xsl:text>
</a>
</td>
<td class="navigation" nowrap="nowrap">
<!-- prevoption is either previous.gif or greyprevious.gif -->
<a>
<img alt="Ïðåäûäóùàÿ ñòðàíèöà" border="0" width="12" height="15" style="vertical-align: text-bottom">
<xsl:attribute name="src">/static/images/greyprevious.gif</xsl:attribute>
</img>
<xsl:text>Ïðåä.</xsl:text>
</a>
</td>
<td class="navigation" nowrap="nowrap">
<a>
<img src="/static/images/all.gif" alt="Ñïèñîê ôîðóìîâ" border="0" width="19" height="15" style="vertical-align: text-bottom" />
<xsl:text>Ñïèñîê</xsl:text>
</a>
</td>
<td class="navigation" nowrap="nowrap">
<!-- nextoption is either next.gif or greynext.gif -->
<a>
<img alt="Ñëåäóþùàÿ ñòðàíèöà" border="0" width="14" height="15" style="vertical-align: text-bottom">
<xsl:attribute name="src">/static/images/next.gif</xsl:attribute>
</img>
<xsl:text>Ñëåä.</xsl:text>
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br/>
<table width="95%" align="center" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td align="left" nowrap="nowrap" width="75%" class="tdheader">Ñîáåñåäíèê</td>
<td nowrap="nowrap" width="5%" class="tdheader" align="center">Ïîñòîâ</td>
<td nowrap="nowrap" width="20%" class="tdheader" align="center">Ïîñëåäíåå</td>
</tr>
<!-- BEGIN POST LOOP DO NOT DELETE -->
<xsl:apply-templates select="conversations/conversation"/>
<!-- END OF LOOP -->
<tr class="tdheader">
<td colspan="5">
<font class="onbody">
<xsl:text>ñòðàíèöû:</xsl:text>
<xsl:apply-templates select="conversations/pageOuter" mode="withCurrent">
<xsl:with-param name="baseLink">/Conversations/</xsl:with-param>
</xsl:apply-templates>
</font>
</td>
</tr>
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="elems\Main.xslt"/>
<xsl:import href="elems\TextEditor.xslt"/>
<xsl:template name="specific">
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td class="tdheader">
<xsl:text>Îòâåò íà ïðèâàòíîå ñîîáùåíèå (</xsl:text>
<xsl:value-of select="message/interlocutor/account/user/name"/>
<xsl:text>)</xsl:text>
</td>
</tr>
<tr class="darktable">
<td>
<xsl:text>Çàïîëíèòå ïðèâåäåííóþ íèæå ôîðìó äëÿ îòïðàâêè ïðèâàòíîãî ñîîáùåíèÿ. HTML îòêëþ÷åí. UBBCode âêëþ÷åí, è âû ìîæåòå èñïîëüçîâàòü UBBCode â âàøèõ ñîîáùåíèÿõ. Àíîíèìíûå ñîîáùåíèÿ ðàçðåøåíû, è âû ìîæåòå âûáðàòü ëþáîå íåçàðåãèñòðèðîâàííîå èìÿ.</xsl:text>
</td>
</tr>
<tr>
<td class="lighttable">
<form method="post" action="/do/SendPM/" name="replier">
<input type="hidden" name="receiverId">
<xsl:attribute name="value"><xsl:value-of select="message/interlocutor/account/id"/></xsl:attribute>
</input>
<xsl:text>Ïîëüçîâàòåëü: </xsl:text>
<xsl:value-of select="session/user/name"/>
<br/>
<br/>
<xsl:text>Òåìà: </xsl:text>
<br/>
<input type="text" tabindex="1" name="title" maxlength="70" class="formboxes" size="60">
<xsl:choose>
<xsl:when test="substring(message/title, 1, 4)='Re: '">
<xsl:attribute name="value"><xsl:value-of select="message/title"/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="value">
<xsl:text>Re: </xsl:text>
<xsl:value-of select="message/title"/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</input>
<br/>
<br/>
<xsl:call-template name="textEditor">
<xsl:with-param name="body">
<xsl:text>[q]</xsl:text>
<xsl:value-of select="message/bodyUBB"/>
<xsl:text>[/q]</xsl:text>
</xsl:with-param>
</xsl:call-template>
<br/>
<input type="checkbox" name="preview" value="1" class="formboxes" disabled="disabled" />
<xsl:text>ß õî÷ó ïðåäâàðèòåëüíî ïðîñìîòðåòü ñîîáùåíèå ïåðåä îòïðàâêîé</xsl:text>
<br/>
<br/>
<input type="checkbox" name="spellcheck" value="1" class="formboxes" onChange="document.replier.preview.checked=this.checked;" disabled="disabled" />
<xsl:text>Ïðîâåðèòü ïðàâîïèñàíèå</xsl:text>
<br/>
<br/>
<input type="submit" tabindex="3" name="textcont" taborder="2" value="Ïðîäîëæèòü" class="buttons"/>
</form>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br/>
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td class="tdheader">
<xsl:text>Îòâåò íà ñîîáùåíèå</xsl:text>
</td>
</tr>
<tr class="darktable">
<td>
<b>
<xsl:text>Àâòîð: </xsl:text>
<xsl:value-of select="message/poster/account/user/name"/>
<br/>
<xsl:text>Òåìà: </xsl:text>
<xsl:value-of select="message/title"/>
</b>
</td>
</tr>
<tr>
<td class="lighttable">
<xsl:value-of select="message/body" disable-output-escaping="yes"/>
</td>
</tr>
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="elems\Main.xslt"/>
<xsl:import href="elems\TextEditor.xslt"/>
<xsl:template name="specific">
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td class="tdheader">
<xsl:text>Îòâåò íà ñîîáùåíèå (</xsl:text>
<xsl:value-of select="board/name"/>
<xsl:text>)</xsl:text>
</td>
</tr>
<tr class="darktable">
<td>
<xsl:text>Çàïîëíèòå ïðèâåäåííóþ íèæå ôîðìó äëÿ îòïðàâêè ñîîáùåíèÿ â ôîðóì. HTML îòêëþ÷åí. UBBCode âêëþ÷åí, è âû ìîæåòå èñïîëüçîâàòü UBBCode â âàøèõ ñîîáùåíèÿõ. Àíîíèìíûå ñîîáùåíèÿ ðàçðåøåíû, è âû ìîæåòå âûáðàòü ëþáîå íåçàðåãèñòðèðîâàííîå èìÿ.</xsl:text>
</td>
</tr>
<tr>
<td class="lighttable">
<form method="post" action="/do/SendPM/" name="replier">
<input type="hidden" name="receiverId">
<xsl:attribute name="value"><xsl:value-of select="receiver/account/id"/></xsl:attribute>
</input>
<xsl:text>Ïîëüçîâàòåëü: </xsl:text>
<xsl:value-of select="session/user/name"/>
<br/>
<xsl:text>Ïîëó÷àòåëü: </xsl:text>
<xsl:value-of select="post/poster/user/name"/>
<br/>
<br/>
<xsl:text>Òåìà: </xsl:text>
<br/>
<input type="text" tabindex="1" name="title" maxlength="70" class="formboxes" size="60">
<xsl:choose>
<xsl:when test="substring(post/title, 1, 4)='Re: '">
<xsl:attribute name="value"><xsl:value-of select="post/title"/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="value">
<xsl:text>Re: </xsl:text>
<xsl:value-of select="post/title"/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</input>
<br/>
<br/>
<xsl:call-template name="textEditor"/>
<br/>
<input type="checkbox" name="preview" value="1" class="formboxes" disabled="disabled" />
<xsl:text>ß õî÷ó ïðåäâàðèòåëüíî ïðîñìîòðåòü ñîîáùåíèå ïåðåä îòïðàâêîé</xsl:text>
<br/>
<br/>
<input type="checkbox" name="spellcheck" value="1" class="formboxes" onChange="document.replier.preview.checked=this.checked;" disabled="disabled" />
<xsl:text>Ïðîâåðèòü ïðàâîïèñàíèå</xsl:text>
<br/>
<br/>
<input type="submit" tabindex="3" name="textcont" taborder="2" value="Ïðîäîëæèòü" class="buttons"/>
</form>
</td>
</tr>
</table>
</td>
</tr>
</table>
<br/>
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td class="tdheader">
<xsl:text>Îòâåò íà ñîîáùåíèå</xsl:text>
</td>
</tr>
<tr class="darktable">
<td>
<b>
<xsl:text>Àâòîð: </xsl:text>
<xsl:value-of select="post/poster/user/name"/>
<br/>
<xsl:text>Òåìà: </xsl:text>
<xsl:value-of select="post/title"/>
</b>
</td>
</tr>
<tr>
<td class="lighttable">
<xsl:value-of select="post/body" disable-output-escaping="yes"/>
</td>
</tr>
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="elems\Main.xslt"/>
<xsl:import href="elems\TextEditor.xslt"/>
<xsl:template name="specific">
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td class="tdheader">
<xsl:text>Íîâîå ïðèâàòíîå ñîîáùåíèå</xsl:text>
</td>
</tr>
<tr class="darktable">
<td>
<xsl:text>Çàïîëíèòå ïðèâåäåííóþ íèæå ôîðìó äëÿ îòïðàâêè ïðèâàòíîãî ñîîáùåíèÿ. HTML îòêëþ÷åí. UBBCode âêëþ÷åí, è âû ìîæåòå èñïîëüçîâàòü UBBCode â âàøèõ ñîîáùåíèÿõ. Àíîíèìíûå ñîîáùåíèÿ ðàçðåøåíû, è âû ìîæåòå âûáðàòü ëþáîå íåçàðåãèñòðèðîâàííîå èìÿ.</xsl:text>
</td>
</tr>
<tr>
<td class="lighttable">
<form method="post" action="/do/SendPM/" name="replier">
<xsl:text>Ïîëüçîâàòåëü: </xsl:text>
<xsl:value-of select="session/user/name"/>
<br/>
<xsl:text>Ïîëó÷àòåëü: </xsl:text>
<input type="text" name="receiverName" class="formboxes" size="15"/>
<br/>
<br/>
<xsl:text>Òåìà: </xsl:text>
<br/>
<input type="text" tabindex="1" name="title" maxlength="70" class="formboxes" size="60"/>
<br/>
<br/>
<xsl:call-template name="textEditor"/>
<br/>
<input type="checkbox" name="preview" value="1" class="formboxes" disabled="disabled" />
<xsl:text>ß õî÷ó ïðåäâàðèòåëüíî ïðîñìîòðåòü ñîîáùåíèå ïåðåä îòïðàâêîé</xsl:text>
<br/>
<br/>
<input type="checkbox" name="spellcheck" value="1" class="formboxes" onChange="document.replier.preview.checked=this.checked;" disabled="disabled" />
<xsl:text>Ïðîâåðèòü ïðàâîïèñàíèå</xsl:text>
<br/>
<br/>
<input type="submit" tabindex="3" name="textcont" taborder="2" value="Ïðîäîëæèòü" class="buttons"/>
</form>
</td>
</tr>
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:template match="conversation">
<tr>
<xsl:choose>
<xsl:when test="position() mod 2 = 0">
<xsl:attribute name="class">lighttable</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="class">darktable</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<td align="left">
<img alt="*" hspace="5" style="vertical-align: text-bottom">
<xsl:choose>
<xsl:when test="afterLastRead and afterLastRead&gt;lastMessageId">
<xsl:attribute name="src">/static/images/message-normal-read.gif</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="src">/static/images/message-normal-notread.gif</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</img>
<a>
<xsl:attribute name="href">
<xsl:text>/Conversation/</xsl:text>
<xsl:value-of select="interlocutor/account/id"/>
<xsl:text>/</xsl:text>
<xsl:if test="afterLastRead&lt;=lastPostId">
<xsl:text>p</xsl:text>
<xsl:value-of select="afterLastRead"/>
</xsl:if>
</xsl:attribute>
<xsl:value-of select="interlocutor/account/user/name"/>
</a>
<span class="small" style="margin-left:1.5em">
<xsl:apply-templates select="pageOuter" mode="withoutCurrent">
<xsl:with-param name="baseLink">/Conversation/<xsl:value-of select="interlocutor/account/id"/>/</xsl:with-param>
</xsl:apply-templates>
</span>
</td>
<td align="center" nowrap="nowrap">
<span class="separate"><xsl:value-of select="totalMessages"/></span>
<xsl:if test="totalNewMessages and totalNewMessages!='0'">
<a class="cup separate">
<font class="new"><i>(<xsl:value-of select="totalNewMessages"/>)</i></font>
</a>
</xsl:if>
</td>
<td nowrap="nowrap" align="center">
<xsl:apply-templates select="lastMessageDate/date" mode="dateTime"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>

@ -34,9 +34,23 @@
<table width="100%" class="tableborders" cellpadding="3" cellspacing="1"> <table width="100%" class="tableborders" cellpadding="3" cellspacing="1">
<tr> <tr>
<td align="center" class="menubar"> <td align="center" class="menubar">
<a href="/Boards/" target="_top">Ñïèñîê ôîðóìîâ</a> <a target="_top">
<xsl:if test="session/sessionKey">
<xsl:attribute name="href">/Conversations/</xsl:attribute>
<xsl:if test="session/indicators/unreadPrivateMessages != '0'">
<img src="/static/images/newpm.gif">
<xsl:attribute name="alt">
<xsl:text>Ó âàñ </xsl:text>
<xsl:value-of select="session/indicators/unreadPrivateMessages"/>
<xsl:text> íåïðî÷èòàííûõ ëè÷íûõ ñîîáùåíèé</xsl:text>
</xsl:attribute>
</img>
</xsl:if>
</xsl:if>
<xsl:text>Ëè÷íûå ñîîáùåíèÿ</xsl:text>
</a>
<xsl:text> | </xsl:text> <xsl:text> | </xsl:text>
<a target="_top">Ïîèñê</a> <a href="/Boards/" target="_top">Ñïèñîê ôîðóìîâ</a>
<xsl:text> | </xsl:text> <xsl:text> | </xsl:text>
<a target="_top"> <a target="_top">
<xsl:if test="session/sessionKey"> <xsl:if test="session/sessionKey">

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="UserInfoBar.xslt"/>
<xsl:template match="message">
<tr>
<td>
<table width="100%" cellspacing="1" cellpadding="3" border="0">
<tr>
<td width="120" valign="top" class="darktable" rowspan="2">
<a><xsl:attribute name="name">Message<xsl:value-of select="id"/></xsl:attribute></a>
<xsl:apply-templates select="poster/account/user" mode="userInfoBar"/>
</td>
<td class="subjecttable">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="left" width="65%" valign="top">
<a target="_blank" class="separate">
<img border="0" alt="" style="vertical-align: text-bottom">
<xsl:choose>
<xsl:when test="isRead='false'">
<xsl:attribute name="src">/static/images/message-normal-notread.gif</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="src">/static/images/message-normal-read.gif</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</img>
</a>
<b class="separate"><xsl:value-of select="title"/></b>
<xsl:if test="isRead='false'">
<img alt="new" src="/static/images/new.gif" />
</xsl:if>
<br />
<font class="small" style="padding-left:2em"><xsl:apply-templates select="postDate/date" mode="dateTime"/></font>
</td>
<td align="right" width="35%">
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="right">
<table class="tablesurround" border="0">
<tr>
<td class="navigation">
<a>
<xsl:attribute name="href">/PMReply/<xsl:value-of select="id"/>/</xsl:attribute>
<img src="/static/images/reply.gif" border="0" alt="Îòâåò íà ñîîáùåíèå" width="27" height="14" title="Îòâåò íà ñîîáùåíèå" style="vertical-align: text-bottom" />
</a>
</td>
<td class="navigation">
<a>
<img src="/static/images/edit.gif" border="0" alt="Ïðàâêà ñîîáùåíèÿ" title="Ïðàâêà ñîîáùåíèÿ" width="21" height="14" style="vertical-align: text-bottom" />
</a>
</td>
<td class="navigation">
<a target="_blank">
<img src="/static/images/print.gif" border="0" alt="Ïå÷àòü ñîîáùåíèÿ" title="Ïå÷àòü ñîîáùåíèÿ" />
</a>
</td>
<td class="navigation">
<a>
<img src="/static/images/email2.gif" border="0" alt="Îòâåòèòü ïðèâàòîì" title="Îòâåòèòü ïðèâàòîì" />
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="lighttable">
<table width="100%" cellspacing="0" cellpadding="0" style="table-layout: fixed">
<tr>
<td>
<br />
<font class="post">
<xsl:value-of select="body" disable-output-escaping="yes" />
<br />
<br />
</font>
</td>
</tr>
<xsl:if test="poster/account/user/signature != ''">
<tr>
<td>
<div style="width:100%;max-height:50px;height: expression( this.scrollHeight > 49 ? '50px' : 'auto' );overflow:hidden">
<font size="-2"><xsl:value-of select="poster/account/user/signature"/><br /></font>
</div>
</td>
</tr>
</xsl:if>
</table>
</td>
</tr>
</table>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>

@ -85,6 +85,9 @@
</td> </td>
<td class="navigation"> <td class="navigation">
<a> <a>
<xsl:if test="/root/session/sessionKey">
<xsl:attribute name="href">/Post/<xsl:value-of select="id"/>/PMReply/</xsl:attribute>
</xsl:if>
<img src="/static/images/email2.gif" border="0" alt="Îòâåòèòü ïðèâàòîì" title="Îòâåòèòü ïðèâàòîì" /> <img src="/static/images/email2.gif" border="0" alt="Îòâåòèòü ïðèâàòîì" title="Îòâåòèòü ïðèâàòîì" />
</a> </a>
</td> </td>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="Windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml">
<xsl:import href="..\elems\Main.xslt"/>
<xsl:template name="specific">
<table width="95%" align="center" cellpadding="1" cellspacing="1" class="tablesurround">
<tr>
<td>
<table cellpadding="3" cellspacing="1" width="100%" class="tableborders">
<tr>
<td class="tdheader">
<xsl:text>Íîâîå ïðèâàòíîå ñîîáùåíèå</xsl:text>
</td>
</tr>
<tr>
<td class="lighttable">
<xsl:text>Âàøå ñîîáùåíèå áûëî óñïåøíî ñîçäàíî</xsl:text>
<br/>
<a>
<xsl:attribute name="href">/Conversation/<xsl:value-of select="message/interlocutor/account/id"/>/p<xsl:value-of select="message/id"/></xsl:attribute>
<xsl:text>Ïðîñìîòðåòü ñîîáùåíèå</xsl:text>
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
Loading…
Cancel
Save