From 66b7fea53bc2f3c82611e242ec9c810386cb1630 Mon Sep 17 00:00:00 2001 From: "Eric J. Bowersox" Date: Wed, 7 Feb 2001 21:12:38 +0000 Subject: [PATCH] * landed support for reading topics and posting followup messages to a topic - the basis of the conferencing engine is now firmly in place * tweaks to the HTML Checker to make it better at breaking lines without leaving stranded punctuation at the beginning or end of a line * also modified dictionary to better handle possessives and hyphenates * as always, miscellaneous tweaks and bugfizes as I spot them --- doc/buttons/bn_go.png | Bin 4526 -> 0 bytes doc/buttons/bn_hide.png | Bin 4536 -> 0 bytes doc/buttons/bn_post_go_next.png | Bin 4908 -> 0 bytes doc/buttons/bn_post_reload.png | Bin 4862 -> 0 bytes doc/buttons/bn_scribble.png | Bin 4679 -> 0 bytes doc/design-goals.html | 2 +- etc/erbo.dict | 97 +++ etc/web.xml | 13 + setup/database.sql | 4 + .../silverwrist/venice/core/TopicContext.java | 5 + .../venice/core/TopicMessageContext.java | 64 ++ .../venice/core/impl/ConferenceBackend.java | 14 +- .../venice/core/impl/ConferenceCoreData.java | 31 + .../venice/core/impl/ConferenceData.java | 7 + .../core/impl/ConferenceSIGContext.java | 7 + .../core/impl/ConferenceSIGContextImpl.java | 30 + .../core/impl/ConferenceUserContextImpl.java | 36 + .../impl/TopicMessageUserContextImpl.java | 706 ++++++++++++++++++ .../core/impl/TopicUserContextImpl.java | 264 ++++++- .../venice/htmlcheck/dict/DictNode.java | 4 +- .../venice/htmlcheck/dict/TreeLexicon.java | 82 +- .../htmlcheck/impl/HTMLCheckerImpl.java | 82 +- .../silverwrist/venice/security/Audit.java | 4 + .../venice/servlets/ConfDisplay.java | 175 ++++- .../venice/servlets/PostMessage.java | 414 ++++++++++ .../venice/servlets/format/PostPreview.java | 190 +++++ .../venice/servlets/format/PostSlippage.java | 216 ++++++ .../venice/servlets/format/TopicListing.java | 17 + .../venice/servlets/format/TopicPosts.java | 354 +++++++++ web/format/posts.jsp | 327 ++++++++ web/format/preview.jsp | 81 ++ web/format/slippage.jsp | 107 +++ web/format/topics.jsp | 46 +- web/images/bn_archive_topic.gif | Bin 0 -> 979 bytes web/images/bn_delete_topic.gif | Bin 0 -> 969 bytes web/images/bn_freeze_topic.gif | Bin 0 -> 1099 bytes web/images/bn_go.gif | Bin 0 -> 879 bytes web/images/bn_hide.gif | Bin 0 -> 906 bytes web/images/bn_hide_topic.gif | Bin 0 -> 969 bytes web/images/bn_next_keep_new.gif | Bin 0 -> 1029 bytes web/images/bn_next_topic.gif | Bin 0 -> 951 bytes web/images/bn_nuke.gif | Bin 0 -> 910 bytes web/images/bn_post_go_next.gif | Bin 0 -> 997 bytes web/images/bn_post_reload.gif | Bin 0 -> 1134 bytes web/images/bn_scribble.gif | Bin 0 -> 949 bytes web/images/bn_show.gif | Bin 0 -> 917 bytes web/images/bn_show_topic.gif | Bin 0 -> 980 bytes web/images/bn_topic_list.gif | Bin 0 -> 950 bytes web/images/bn_unarchive_topic.gif | Bin 0 -> 1004 bytes web/images/bn_unfreeze_topic.gif | Bin 0 -> 1015 bytes 50 files changed, 3304 insertions(+), 75 deletions(-) delete mode 100644 doc/buttons/bn_go.png delete mode 100644 doc/buttons/bn_hide.png delete mode 100644 doc/buttons/bn_post_go_next.png delete mode 100644 doc/buttons/bn_post_reload.png delete mode 100644 doc/buttons/bn_scribble.png create mode 100644 src/com/silverwrist/venice/core/TopicMessageContext.java create mode 100644 src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java create mode 100644 src/com/silverwrist/venice/servlets/PostMessage.java create mode 100644 src/com/silverwrist/venice/servlets/format/PostPreview.java create mode 100644 src/com/silverwrist/venice/servlets/format/PostSlippage.java create mode 100644 src/com/silverwrist/venice/servlets/format/TopicPosts.java create mode 100644 web/format/posts.jsp create mode 100644 web/format/preview.jsp create mode 100644 web/format/slippage.jsp create mode 100644 web/images/bn_archive_topic.gif create mode 100644 web/images/bn_delete_topic.gif create mode 100644 web/images/bn_freeze_topic.gif create mode 100644 web/images/bn_go.gif create mode 100644 web/images/bn_hide.gif create mode 100644 web/images/bn_hide_topic.gif create mode 100644 web/images/bn_next_keep_new.gif create mode 100644 web/images/bn_next_topic.gif create mode 100644 web/images/bn_nuke.gif create mode 100644 web/images/bn_post_go_next.gif create mode 100644 web/images/bn_post_reload.gif create mode 100644 web/images/bn_scribble.gif create mode 100644 web/images/bn_show.gif create mode 100644 web/images/bn_show_topic.gif create mode 100644 web/images/bn_topic_list.gif create mode 100644 web/images/bn_unarchive_topic.gif create mode 100644 web/images/bn_unfreeze_topic.gif diff --git a/doc/buttons/bn_go.png b/doc/buttons/bn_go.png deleted file mode 100644 index dac836f0c0b249168a32064fc52b1134cb3f0755..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4526 zcmV;f5mD}mP)M7wA2z(7A#ub>P4$1kdTm&K!8}VKmt}MEC2zKj2tsKNt4M*?6S*Mc2(Vb z&pp$3zVnUm{PrTvPk8h^5B%N#^W0~C=|9a+o_hS@gW3Fe?A!X#TwXqfG3~mM;%Kc; z(^O>ho5%Ny>KsO^rvX4X^&R7)9~39hSf7YwnzXZq5H^mT;KDjfDTRn57m3r(|6 z(=^6_AaLFnvy=U1$+_6?_jz7H6r6Qj3WRA>?;r?Cny;Z#&LugGmJ4ykU+rd zwmCpVr^yTEirL&5zprVI2ZMPUlbKW!TUfNgP1n;h9D@D+(+>E z@dk$Q=l8z)T2|iS6y7YCi_`NWFPlTpa8SxJ#?o@JzJ4f@BLuxw6T-Nuw?$Dkhc4lC z7}czpHO)Tw5CNRVUJBuZPm( zo^Xy4q7a6%I@)bkMKzn0LI_Yq0w7t+`l0XImJn_XVAL07<*e)bo^i$~an5^hv+}6h zZ<92w_xrM(6C91!gfake-_(R)ArtF-uo^+u4<i~#(b-dr)3Mt#RlQKaFIBR5@DQ)|vmQo;0wRI2#2q__P zn$)NW8ioOY`P2`_xO)9K&1ZtK^7#DGgL`Fl79()Fg$QkySJiy6-8KaHanx0oZdSKe zPZ8juJ4nG1rd$ZEt=7sK7lVr+#ux~}lrlm{3}F}t#sx-zV5;?0&5o{~Jd#2N9|@%x z;LB?rp_It|2I8F%q+0jCedK?D;fHm4`ixAAK<7T^JT-3W-2uTRCS~DSsK}~H# z2tkNy<3J2CV1nZ~PP1wT5k~3JF#vlXz|b{WURkB9#pOHS_;Q|?F#$>sY5hA8>l$;?<7*G0s^xk<7LU7Ip?>&T&5{fYbAt5PgIYfbV=u zFvh_;rS(7l%cEcT&!2n!&8yFU;lVHdm(P9ut@qM2)mn4GgR_G&Wu7Umn!1LFsA{RDD=1^G+8g7%#RnV+fBj<#Hx7`_| z8IyhA2}Us?go$bFop%sYjH9u^dq)W2Tp$F4cLBuprvLpv`O4q@#V^kacDy+L^S}J! zAO7h#e(QhycwUs=nnBq-ON_I9*C9ll(HNuI(Mb$`9ELQ@LGW4|!5JcKIMfJ}-F8=$ z6@t-Q2N6-)j%|~sDTH_!rx?6m5{zQX#y=Fe3yR=Rgb?6CqLv z1BgCE;H>%E*B)M8R6p_q7bmAD*VorCy?7^t@bOokxxTJ{?Kl4VH-Gny`wzE2@`HDR z_rW_t$b4}Uoa+vUtSn%RUE2xH5T;Y#AxsX3wyNeJdI*5A-a6kmb&@0)VQsCq)*2%v zhlmKlDC2$GYHcYc{b0~XzvrXPYI}5g`QdwSAxK4Xv|BwC$!u(QX`1i%y`b0!I4g?H zdabplOipS7A#f>}%*LU0-UCP&C)yeSAVNsW6u}TgMyWKS0RUOwynB55k#4^dGU-Rn2^`1ics}2(H_kdPQfmzGR;CG0@@eR_(U>sKsSh4w z2BPOY!6ZTiF(J-bgh`g?&imPXzFgjbBPYA|&Ideu0eLtHY!3esxqZA{`^w&`-bm*yJ|KT5e?w`MO zKS^_~$3Og?SAY9|{UiXO%rgK$2(fPu7()a>R#x8Xu5HU|1|b~!3Bm|5acV*c9}WkZ zq!c4-t=7gE-#7a-Nu`u+Hv}K7Hk2|c69gf|#CuooYQ{N;!8#wjhajQ{U;mr*{H1a7 zL%U3hi^KLR$?|SADA=y+FV64WE^lK9X;y{ko4U@5BFW3H-D|A?q-i23XIeS$Rgz{& z!Xbc6h-n-s<7t-e4~M7kKJ|&0i?`paA75>bPR@*;zVg;J06-h{%J-hV`Q`U1CEI$s zIG@GfyRMrbFBs!pJA_~`q0u`CalPAdE(wANA@76Hc4(V4O@$EsFoh7Nq34_nnNUJ} z01$xnaw(+*0D2FcwUl54A$j)WKMkV3`rxf+Kl%gh=Aq28x*0M-t{*R7{@C|DeE&fR zo{KC--!=Qi=?P{@d#JUZ2$f}(V@le?5Ti-bTnOrnrJN7#fpN|`YntwvXYStITtEHP z@vpr4y}$6QpZl4ge9rl}tEZ2>cnT2y%+EjnzklanU;gOHx9)BJ?2lgU+IGG;;f(Kh zbqod~gdl*BEN`x}q9PbUh$f|`Vbn@XDW#B;w$@s$CLxkEFF2#tM@q?RxfD_&jE%7& z7|tk!P-zDrefz&tY^(E6K79LMWST8+Zcpa3^>X#x^DjJk^u$_&F{cdI>-E{i1tg+9 z)Z;WD#ELQ_j1IjDK{GCsgc)rZaEPkijDJPlbykv=E|-jRA%t_zIVS~&2pa462Lr$UPrvrcr~m8M-uUCQ zi_1qJJSx&;yI#Na(kmZ4_@HkONj?M7k6n9wazaG5+uUkB1%Qtib3$p~D`!WUWSnEI zw2*Qdddj$zVz+J1@0@NoTgI99?(E`Dz1b+^XI0*|Z3Iw_6UK;AhB0PL4E;f71;tn^ zqty_+7eYXYZ?3O6=Tb-^W!H7i+P>>K=Ve)Hhb=2_wRn>?ryz({)HFszx{1%U0NK0=!W)C&Sv>+ zvA(@hdU6h)oE<}q)nv8mk~HTWO{2>4eCS)oIOn`=htspgcDv)88KW*PpWd!l&VwRP zyMA!qdFx~JjB)^>OwzG$v!a9%SPQJ~tTmJ}2;j~2HRnQbE~V`I-WaRK31eJUmA0-Q z1`q;cG%HGs(KtEcZP#a6iV%AE;RgT2Cl42Ai?4n4o6kP` zydKqeJ^RAr2k#qW2}>wLLwlGV9VO+_ZhJdULx}j|&KW?^nxIsh<|SiLPb$e$qZMU5 zP4mN{J3T+y?sifNt<}ZlQ``00xR^*fP9}ihtVak?!Vtkpmg#8}Ns0gn0ed$XtvTo3 z$9lUZlyc4l7t=J2!x)^$lvHIk8Qb?g0stXYlrxNB+x1zV-8{JxNs^^0f>>*nC6Z8f zI2@$lAjU~e-JsDkcb|FZ+i#zrp4aQ`ou{68^x%D^v^7phK6c&7*=asMS>HVB`!+;; zc5wm;vNmen<;4tRua!=+%vpm8&ho0R>&4mecDv=AS!?b*{q$zN($-6kjRS;{;3MaR z3yCOB)68lkvy@;2APs&ThCv7c0BrU(BA7GA8CP0Q;{+i>80Tr$kJHfiAVvT{Sw%NIp>VU5ZgnOCX#Yd@3um41mUCzfe5 zw(ZiQ(Bn|eXGyhKUw<%Z9RNN(TTsrX(HY%mMFoMi#-&;2oxzwSX|dgI&d*P`+YMtZ zMsVk;%hk=bk4Oq)T;x)W!^kK?hcbPMgZIPkR)mJK1q|{gVDzO001b9Vi=V%=HE*$5k~c{&WpU>ZbOV!RmI?& zW`D9c7Babh@;FHn5aTqClv2jH5E6m_|L@=bdYQ9#-(L+wzuD~Oi$$>JcySzQQm?P9 zv(}@dqY@LW4H){mtd3$dYEor2vqlj@G2zXAzc^W}*GnNJg82C4c)hxf5n&VoAe2)z zO@v?wpz}^9X>fr{;j94>$6&j*O|#Tlr=}@{;GJWPd+)7r!Fz--XVf@94kL&WLYU=w zjG%4XEYHWGx6T)N7D7<{9 diff --git a/doc/buttons/bn_hide.png b/doc/buttons/bn_hide.png deleted file mode 100644 index 7739c59fed57d2f92ca129ee8235aeb46502dc99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4536 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 Wd(8$`J$mH;0000X@FoU*aE#$ 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 zsOutput uses XML and gets formatted into [X]HTML via XSLT; "themeable"/"skinnable" interface
  • Conferencing/other functionality available via XML-RPC or SOAP calls
  • News page creation (see Slash, Squishdot, Scoop)
  • -
  • Member trust metrics (see Slashdot "karma," Scoop "mojo," Advogato distributed trust metric)
  • +
  • Member trust metrics (see Slashdot "karma," Scoop "mojo," Advogato distributed trust metric) (Feature indefinitely shelved at the request of the EMinds community)
  • User diary pages (see Advogato, Kuro5hin)
  • Moderated discussions (see Slash, Scoop)
  • Collaborative database facility (see Wiki, Everything2)
  • diff --git a/etc/erbo.dict b/etc/erbo.dict index d0c9b0f..7a6d8e7 100644 --- a/etc/erbo.dict +++ b/etc/erbo.dict @@ -1,8 +1,105 @@ +advogato +ain't +anla'shok +bajor +bios +boitano +boromax +can't +cartman +checkouts +couldn't +crewmember +crewmembers +deflector +deflectors +delenn +didn't +dilithium +docking +doesn't +don't eminds +entil'zha +eps erbo +fett +followup +franklin +fucking +hairstyle +hairstyles +hasn't +he'd +html +i'd +i'll +i'm +i've +inducers +it'd +khan +kubla +kyle +lafou +ma'am maddog +marillion +minbar +minbari +mr +mustn't +nacelle +nacelles +navigational +ops +padd +peachy +planitia +planum +psi +refit +refitting +replicator +repost +reposted +rom +runabout +salchow +salchows +sarcastically +silverwrist snarf snarfage +snazzy +sourceforge +spaceport +spellchecker +spellchucker +spelunker +spelunkers +st +starbase +starfleet +starship +straightening +stunted +terrence +they're +turbolift +tuzanor +umbilical +umbilicals +url utne +valen +veni +we'll +we're webb webbme +won't +wouldn't +you'd +you'll +you're diff --git a/etc/web.xml b/etc/web.xml index c4e65e7..89401ff 100644 --- a/etc/web.xml +++ b/etc/web.xml @@ -142,6 +142,14 @@ com.silverwrist.venice.servlets.ConfDisplay + + postmessage + + Posting messages to a conference. + + com.silverwrist.venice.servlets.PostMessage + + @@ -200,6 +208,11 @@ /confdisp + + postmessage + /post + + testformdata diff --git a/setup/database.sql b/setup/database.sql index d9c47de..e2b242e 100644 --- a/setup/database.sql +++ b/setup/database.sql @@ -416,6 +416,10 @@ INSERT INTO refaudit (type, descr) VALUES (307, 'Delete Topic'), (308, 'Set Topic Frozen'), (309, 'Set Topic Archive'), + (310, 'Post Message'), + (311, 'Hide Message'), + (312, 'Scribble Message'), + (313, 'Nuke Message'), (9999999, 'DUMMY'); # The ISO 3166 two-letter country codes. Source is diff --git a/src/com/silverwrist/venice/core/TopicContext.java b/src/com/silverwrist/venice/core/TopicContext.java index c3e36dd..b95e37a 100644 --- a/src/com/silverwrist/venice/core/TopicContext.java +++ b/src/com/silverwrist/venice/core/TopicContext.java @@ -64,4 +64,9 @@ public interface TopicContext public abstract void fixSeen() throws DataException; + public abstract List getMessages(int low, int high) throws DataException, AccessError; + + public abstract TopicMessageContext postNewMessage(long parent, String pseud, String text) + throws DataException, AccessError; + } // end interface TopicContext diff --git a/src/com/silverwrist/venice/core/TopicMessageContext.java b/src/com/silverwrist/venice/core/TopicMessageContext.java new file mode 100644 index 0000000..6e8a01a --- /dev/null +++ b/src/com/silverwrist/venice/core/TopicMessageContext.java @@ -0,0 +1,64 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Community System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.core; + +import java.util.Date; + +public interface TopicMessageContext +{ + public abstract long getPostID(); + + public abstract long getParentPostID(); + + public abstract int getPostNumber(); + + public abstract int getNumLines(); + + public abstract int getCreatorUID(); + + public abstract String getCreatorName() throws DataException; + + public abstract Date getPostDate(); + + public abstract boolean isHidden(); + + public abstract boolean isScribbled(); + + public abstract boolean isNuked(); + + public abstract Date getScribbleDate(); + + public abstract String getPseud(); + + public abstract String getBodyText() throws DataException; + + public abstract boolean hasAttachment(); + + public abstract boolean canHide(); + + public abstract boolean canScribble(); + + public abstract boolean canNuke(); + + public abstract void setHidden(boolean flag) throws DataException, AccessError; + + public abstract void scribble() throws DataException, AccessError; + + public abstract void nuke() throws DataException, AccessError; + +} // end interface TopicMessageContext diff --git a/src/com/silverwrist/venice/core/impl/ConferenceBackend.java b/src/com/silverwrist/venice/core/impl/ConferenceBackend.java index a04866a..14fdcdc 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceBackend.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceBackend.java @@ -17,8 +17,10 @@ */ package com.silverwrist.venice.core.impl; -import java.sql.*; +import java.sql.Connection; +import java.sql.SQLException; import java.util.Date; +import com.silverwrist.venice.core.DataException; public interface ConferenceBackend extends SIGBackend { @@ -32,4 +34,14 @@ public interface ConferenceBackend extends SIGBackend public abstract String realConfAlias(); + public abstract boolean userCanScribble(); + + public abstract boolean userCanNuke(); + + public abstract boolean userCanRead(); + + public abstract boolean userCanPost(); + + public abstract void touchUpdate(Connection conn, java.util.Date date) throws DataException; + } // end interface ConferenceBackend diff --git a/src/com/silverwrist/venice/core/impl/ConferenceCoreData.java b/src/com/silverwrist/venice/core/impl/ConferenceCoreData.java index bc48d4a..2830939 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceCoreData.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceCoreData.java @@ -932,6 +932,37 @@ class ConferenceCoreData implements ConferenceData } // end createNewTopic + public boolean canScribblePosts(int level) + { + return (level>=nuke_level); + + } // end canScribblePosts + + public boolean canNukePosts(int level) + { + return (level>=nuke_level); + + } // end canNukePosts + + public synchronized void touchUpdate(Connection conn, java.util.Date date) throws DataException + { + try + { // update the last update date + Statement stmt = conn.createStatement(); + StringBuffer sql = new StringBuffer("UPDATE confs SET lastupdate = '"); + sql.append(SQLUtil.encodeDate(date)).append("' WHERE confid = ").append(confid).append(';'); + stmt.executeUpdate(sql.toString()); + last_update = date; + + } // end try + catch (SQLException e) + { // convert the SQLException + throw new DataException("Database error updating conference: " + e.getMessage(),e); + + } // end catch + + } // end touchUpdate + /*-------------------------------------------------------------------------------- * External static operations (usable only from within package) *-------------------------------------------------------------------------------- diff --git a/src/com/silverwrist/venice/core/impl/ConferenceData.java b/src/com/silverwrist/venice/core/impl/ConferenceData.java index 6765d21..3e38d3e 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceData.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceData.java @@ -17,6 +17,7 @@ */ package com.silverwrist.venice.core.impl; +import java.sql.Connection; import java.util.Date; import java.util.List; import com.silverwrist.venice.core.DataException; @@ -79,4 +80,10 @@ public interface ConferenceData extends ReferencedData public abstract ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body, int body_lines) throws DataException; + public abstract boolean canScribblePosts(int level); + + public abstract boolean canNukePosts(int level); + + public abstract void touchUpdate(Connection conn, Date date) throws DataException; + } // end interface ConferenceData diff --git a/src/com/silverwrist/venice/core/impl/ConferenceSIGContext.java b/src/com/silverwrist/venice/core/impl/ConferenceSIGContext.java index 80728c2..47e4e8b 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceSIGContext.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceSIGContext.java @@ -17,6 +17,7 @@ */ package com.silverwrist.venice.core.impl; +import java.sql.Connection; import java.util.Date; import java.util.List; import com.silverwrist.venice.core.DataException; @@ -91,4 +92,10 @@ public interface ConferenceSIGContext extends ReferencedData public abstract ReturnTopicInfo createNewTopic(SIGBackend sig, String title, String pseud, String body, int body_lines) throws DataException; + public abstract boolean canScribblePosts(int level); + + public abstract boolean canNukePosts(int level); + + public abstract void touchUpdate(Connection conn, Date date) throws DataException; + } // end interface ConferenceSIGContext diff --git a/src/com/silverwrist/venice/core/impl/ConferenceSIGContextImpl.java b/src/com/silverwrist/venice/core/impl/ConferenceSIGContextImpl.java index 91ab121..d5c146f 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceSIGContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceSIGContextImpl.java @@ -618,4 +618,34 @@ class ConferenceSIGContextImpl implements ConferenceSIGContext } // end createNewTopic + public boolean canScribblePosts(int level) + { + ConferenceData c = getConferenceDataNE(); + if (c==null) + return false; + if (level. + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Community System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.core.impl; + +import java.sql.*; +import java.util.*; +import org.apache.log4j.*; +import com.silverwrist.venice.db.*; +import com.silverwrist.venice.security.AuditRecord; +import com.silverwrist.venice.core.*; + +class TopicMessageUserContextImpl implements TopicMessageContext +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Category logger = Category.getInstance(TopicMessageUserContextImpl.class.getName()); + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private EngineBackend engine; + private ConferenceBackend conf; + private DataPool datapool; + private long postid; + private long parent; + private int num; + private int linecount; + private int creator_uid; + private java.util.Date posted; + private boolean hidden; + private int scribble_uid; + private java.util.Date scribble_date; + private String pseud; + private int datalen; + private String filename; + private String mimetype; + private boolean nuked = false; + private String creator_cache = null; + private String text_cache = null; + + /*-------------------------------------------------------------------------------- + * Constructors + *-------------------------------------------------------------------------------- + */ + + protected TopicMessageUserContextImpl(EngineBackend engine, ConferenceBackend conf, DataPool datapool, + long postid, long parent, int num, int linecount, int creator_uid, + java.util.Date posted, boolean hidden, int scribble_uid, + java.util.Date scribble_date, String pseud, int datalen, + String filename, String mimetype) + { + this.engine = engine; + this.conf = conf; + this.datapool = datapool; + this.postid = postid; + this.parent = parent; + this.num = num; + this.linecount = linecount; + this.creator_uid = creator_uid; + this.posted = posted; + this.hidden = hidden; + this.scribble_uid = scribble_uid; + this.scribble_date = scribble_date; + this.pseud = pseud; + this.datalen = datalen; + this.filename = filename; + this.mimetype = mimetype; + + } // end constructor + + TopicMessageUserContextImpl(EngineBackend engine, ConferenceBackend conf, DataPool datapool, + long postid, long parent, int num, int linecount, int creator_uid, + java.util.Date posted, String pseud) + { + this.engine = engine; + this.conf = conf; + this.datapool = datapool; + this.postid = postid; + this.parent = parent; + this.num = num; + this.linecount = linecount; + this.creator_uid = creator_uid; + this.posted = posted; + this.hidden = false; + this.scribble_uid = 0; + this.scribble_date = null; + this.pseud = pseud; + this.datalen = 0; + this.filename = null; + this.mimetype = null; + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal functions + *-------------------------------------------------------------------------------- + */ + + private static String quickGetUserName(Connection conn, int uid) throws SQLException + { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT username FROM users WHERE uid = " + String.valueOf(uid) + ";"); + if (rs.next()) + return rs.getString(1); + else + return "(unknown)"; + + } // end quickGetUserName + + private void refresh(Connection conn) throws SQLException + { + Statement stmt = conn.createStatement(); + StringBuffer sql = new StringBuffer("SELECT p.hidden, p.scribble_uid, p.scribble_date, p.pseud, " + + "a.datalen, a.filename, a.mimetype FROM posts p LEFT JOIN " + + "postattach a ON p.postid = a.postid WHERE p.postid = "); + sql.append(postid).append(';'); + ResultSet rs = stmt.executeQuery(sql.toString()); + if (rs.next()) + { // update a variety of fields + hidden = rs.getBoolean(1); + scribble_uid = rs.getInt(2); + scribble_date = SQLUtil.getFullDateTime(rs,3); + pseud = rs.getString(4); + datalen = rs.getInt(5); + filename = rs.getString(6); + mimetype = rs.getString(7); + + } // end if + else + { // the post has been nuked - update accordingly + linecount = 0; + creator_uid = -1; + posted = null; + hidden = false; + scribble_uid = -1; + scribble_date = null; + pseud = null; + datalen = 0; + filename = null; + mimetype = null; + nuked = true; + creator_cache = null; + text_cache = null; + + } // end else + + } // end refresh + + /*-------------------------------------------------------------------------------- + * Implementations from interface TopicMessageContext + *-------------------------------------------------------------------------------- + */ + + public long getPostID() + { + return postid; + + } // end getPostID + + public long getParentPostID() + { + return parent; + + } // end getParentPostID + + public int getPostNumber() + { + return num; + + } // end getPostNumber + + public int getNumLines() + { + return linecount; + + } // end getNumLines + + public int getCreatorUID() + { + return creator_uid; + + } // end getCreatorUID + + public String getCreatorName() throws DataException + { + if (creator_cache==null) + { // we don't have the user name yet, get it out of the database + if (nuked) + return null; // post nuked! + Connection conn = null; + + try + { // use a database connection to get the user name + conn = datapool.getConnection(); + refresh(conn); + if (nuked) + return null; // post nuked! + creator_cache = quickGetUserName(conn,creator_uid); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error reading user name: " + e.getMessage(),e); + throw new DataException("unable to retrieve user name: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end if + + return creator_cache; + + } // end getCreatorName + + public java.util.Date getPostDate() + { + return posted; + + } // end getPostDate + + public boolean isHidden() + { + return hidden; + + } // end isHidden + + public boolean isScribbled() + { + return (scribble_date!=null); + + } // end isScribbled + + public boolean isNuked() + { + return nuked; + + } // end isNuked + + public java.util.Date getScribbleDate() + { + return scribble_date; + + } // end getScribbleDate + + public String getPseud() + { + return pseud; + + } // return pseud + + public String getBodyText() throws DataException + { + if (text_cache==null) + { // we don't have the body text yet, go get it + Connection conn = null; + if (nuked) + return null; // post nuked! + + try + { // use a database connection to get the body text + conn = datapool.getConnection(); + refresh(conn); + + if (nuked) + return null; // post nuked! + + if (scribble_date==null) + { // let's go get the body text! + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT data FROM postdata WHERE postid = " + + String.valueOf(postid) + ";"); + if (rs.next()) + text_cache = rs.getString(1); + else + return "Data Missing"; // FUTURE: throw an exception? + + } // end if + else // for scribbled posts, we return the scribbler's name only + text_cache = quickGetUserName(conn,scribble_uid); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error reading post data: " + e.getMessage(),e); + throw new DataException("unable to retrieve post data: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end if + + return text_cache; + + } // end getBodyText + + public boolean hasAttachment() + { + return (mimetype!=null); + + } // end hasAttachment + + public boolean canHide() + { + return ((creator_uid==conf.realUID()) || conf.userCanHide()); + + } // end canHide + + public boolean canScribble() + { + return ((creator_uid==conf.realUID()) || conf.userCanScribble()); + + } // end canScribble + + public boolean canNuke() + { + return conf.userCanNuke(); + + } // end canNuke + + public void setHidden(boolean flag) throws DataException, AccessError + { + if ((creator_uid!=conf.realUID()) && !(conf.userCanHide())) + { // we can't change the hidden status! + logger.error("trying to set hidden status of post w/o permission!"); + throw new AccessError("You are not permitted to change the hidden status of this message."); + + } // end if + + if (nuked || (scribble_date!=null)) + return; // changing the status of a nuked or scribbled post is futile + + Connection conn = null; + AuditRecord ar = null; + + try + { // open up a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + // lock the tables we reference + stmt.executeUpdate("LOCK TABLES posts WRITE, postattach READ;"); + try + { // first, make sure we have the right status for our post + refresh(conn); + if (nuked || (scribble_date!=null)) + return; // changing the status of a nuked or scribbled post is futile + if (hidden==flag) + return; // this is a no-op + + // update the "hidden" flag in the database + StringBuffer sql = new StringBuffer("UPDATE posts SET hidden = "); + sql.append(flag ? '1' : '0').append(" WHERE postid = ").append(postid).append(';'); + stmt.executeUpdate(sql.toString()); + + hidden = flag; // store flag + + } // end try + finally + { // unlock the tables before we go + Statement ulk_stmt = conn.createStatement(); + ulk_stmt.executeUpdate("UNLOCK TABLES;"); + + } // end finally + + // record what we did in an audit record + ar = new AuditRecord(AuditRecord.HIDE_MESSAGE,conf.realUID(),conf.userRemoteAddress(), + conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post=" + + String.valueOf(postid),flag ? "hide" : "unhide"); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error setting hidden status: " + e.getMessage(),e); + throw new DataException("unable to set hidden status: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + try + { // save off the audit record before we go, though + if ((ar!=null) && (conn!=null)) + ar.store(conn); + + } // end try + catch (SQLException e) + { // we couldn't store the audit record! + logger.error("DB error saving audit record: " + e.getMessage(),e); + + } // end catch + + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end setHidden + + public void scribble() throws DataException, AccessError + { + if ((creator_uid!=conf.realUID()) && !(conf.userCanScribble())) + { // we can't scribble this post + logger.error("trying to scribble post w/o permission!"); + throw new AccessError("You are not permitted to scribble this message."); + + } // end if + + if (nuked || (scribble_date!=null)) + return; // scribbling a nuked or scribbled post is futile + + Connection conn = null; + AuditRecord ar = null; + + try + { // open up a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + // lock the tables we reference + stmt.executeUpdate("LOCK TABLES posts WRITE, postdata WRITE, postattach WRITE;"); + try + { // first, make sure we have the right status for our post + refresh(conn); + if (nuked || (scribble_date!=null)) + return; // scribbling a nuked or scribbled post is futile + + // First, set the appropriate "scribbled" information in the "header". + StringBuffer sql = new StringBuffer("UPDATE posts SET linecount = 0, hidden = 0, scribble_uid = "); + sql.append(conf.realUID()).append(", scribble_date = '"); + java.util.Date now = new java.util.Date(); + final String scribble_pseud = "(Scribbled)"; // TODO: configurable option + sql.append(SQLUtil.encodeDate(now)).append("', pseud = '").append(scribble_pseud); + sql.append("' WHERE postid = ").append(postid).append(';'); + if (logger.isDebugEnabled()) + logger.debug("SQL: " + sql.toString()); + stmt.executeUpdate(sql.toString()); + + // Determine if we need to "rub out" the post before we delete it. + ResultSet rs = stmt.executeQuery("SELECT LENGTH(data) FROM postdata WHERE postid = " + + String.valueOf(postid) + ";"); + if (rs.next()) + { // use this data to overwrite the post with X's + int len = rs.getInt(1); + if (len>0) + { // construct the "rubout" statement and execute it + sql.setLength(0); + sql.append("UPDATE postdata SET data = '"); + while (len>0) + { // generate a string of X's the length of the post + sql.append('X'); + len--; + + } // end while + + sql.append("' WHERE postid = ").append(postid).append(';'); + stmt.executeUpdate(sql.toString()); + + } // end if + // else not much need to do a rubout + + } // end if + // else don't try...we're deleting the row anyway + + // Delete the actual post data row. + sql.setLength(0); + sql.append("DELETE FROM postdata WHERE postid = ").append(postid).append(';'); + stmt.executeUpdate(sql.toString()); + + // Delete the attachment data row. + // FUTURE: can we do an overwrite on the attachment the way we did on the post data? + sql.setLength(0); + sql.append("DELETE FROM postattach WHERE postid = ").append(postid).append(';'); + stmt.executeUpdate(sql.toString()); + + // Update our internal data fields. + linecount = 0; + hidden = false; + scribble_uid = conf.realUID(); + scribble_date = now; + pseud = scribble_pseud; + text_cache = null; + + } // end try + finally + { // unlock the tables before we go + Statement ulk_stmt = conn.createStatement(); + ulk_stmt.executeUpdate("UNLOCK TABLES;"); + + } // end finally + + // record what we did in an audit record + ar = new AuditRecord(AuditRecord.SCRIBBLE_MESSAGE,conf.realUID(),conf.userRemoteAddress(), + conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post=" + + String.valueOf(postid)); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error scribbling post: " + e.getMessage(),e); + throw new DataException("unable to scribble message: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + try + { // save off the audit record before we go, though + if ((ar!=null) && (conn!=null)) + ar.store(conn); + + } // end try + catch (SQLException e) + { // we couldn't store the audit record! + logger.error("DB error saving audit record: " + e.getMessage(),e); + + } // end catch + + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end scribble + + public void nuke() throws DataException, AccessError + { + if (!(conf.userCanNuke())) + { // we can't scribble this post + logger.error("trying to nuke post w/o permission!"); + throw new AccessError("You are not permitted to nuke this message."); + + } // end if + + if (nuked) + return; // nuking a nuked post is futile + + Connection conn = null; + AuditRecord ar = null; + + try + { // open up a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + // lock the tables we reference + stmt.executeUpdate("LOCK TABLES posts WRITE, postdata WRITE, postattach WRITE, postdogear WRITE;"); + + try + { // first, make sure we have the right status for our post + refresh(conn); + if (nuked) + return; // nuking a nuked post is futile + + // Delete any and all references to this post! + stmt.executeUpdate("DELETE FROM posts WHERE postid = " + String.valueOf(postid) + ";"); + stmt.executeUpdate("DELETE FROM postdata WHERE postid = " + String.valueOf(postid) + ";"); + stmt.executeUpdate("DELETE FROM postattach WHERE postid = " + String.valueOf(postid) + ";"); + stmt.executeUpdate("DELETE FROM postdogear WHERE postid = " + String.valueOf(postid) + ";"); + + // Update our internal variables. + linecount = 0; + creator_uid = -1; + posted = null; + hidden = false; + scribble_uid = -1; + scribble_date = null; + pseud = null; + datalen = 0; + filename = null; + mimetype = null; + nuked = true; + creator_cache = null; + text_cache = null; + + } // end try + finally + { // unlock the tables before we go + Statement ulk_stmt = conn.createStatement(); + ulk_stmt.executeUpdate("UNLOCK TABLES;"); + + } // end finally + + // record what we did in an audit record + ar = new AuditRecord(AuditRecord.NUKE_MESSAGE,conf.realUID(),conf.userRemoteAddress(), + conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",post=" + + String.valueOf(postid)); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error nuking post: " + e.getMessage(),e); + throw new DataException("unable to nuke message: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + try + { // save off the audit record before we go, though + if ((ar!=null) && (conn!=null)) + ar.store(conn); + + } // end try + catch (SQLException e) + { // we couldn't store the audit record! + logger.error("DB error saving audit record: " + e.getMessage(),e); + + } // end catch + + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + } // end nuke + + /*-------------------------------------------------------------------------------- + * External static operations + *-------------------------------------------------------------------------------- + */ + + static List loadMessageRange(EngineBackend engine, ConferenceBackend conf, DataPool datapool, int topicid, + int post_low, int post_high) throws DataException + { + if (logger.isDebugEnabled()) + logger.debug("loadMessageRange for conf # " + String.valueOf(conf.realConfID()) + ", topic #" + + String.valueOf(topicid) + ", range [" + String.valueOf(post_low) + ", " + + String.valueOf(post_high) + "]"); + + Vector rc = new Vector(); + Connection conn = null; // pooled database connection + + try + { // get a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + // run a query to get all the posts in a particular topic + StringBuffer sql = + new StringBuffer("SELECT p.postid, p.parent, p.num, p.linecount, p.creator_uid, p.posted, " + + "p.hidden, p.scribble_uid, p.scribble_date, p.pseud, a.datalen, a.filename, " + + "a.mimetype FROM posts p LEFT JOIN postattach a ON p.postid = a.postid " + + "WHERE p.topicid = "); + sql.append(topicid).append(" AND p.num >= ").append(post_low).append(" AND p.num <= "); + sql.append(post_high).append(" ORDER BY p.num ASC;"); + if (logger.isDebugEnabled()) + logger.debug("SQL: " + sql.toString()); + ResultSet rs = stmt.executeQuery(sql.toString()); + + while (rs.next()) + { // create implementation objects and shove them into the return vector + TopicMessageContext val = + new TopicMessageUserContextImpl(engine,conf,datapool,rs.getLong(1),rs.getLong(2),rs.getInt(3), + rs.getInt(4),rs.getInt(5),SQLUtil.getFullDateTime(rs,6), + rs.getBoolean(7),rs.getInt(8),SQLUtil.getFullDateTime(rs,9), + rs.getString(10),rs.getInt(11),rs.getString(12),rs.getString(13)); + rc.add(val); + + } // end while + + } // end try + catch (SQLException e) + { // turn SQLException into data exception + logger.error("DB error reading message entries: " + e.getMessage(),e); + throw new DataException("unable to retrieve messages: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + return new ReadOnlyVector(rc); // wrap the return vector + + } // end loadMessageRange + +} // end class TopicMessageUserContextImpl diff --git a/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java b/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java index da31a20..9331c16 100644 --- a/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java @@ -21,6 +21,7 @@ import java.sql.*; import java.util.*; import org.apache.log4j.*; import com.silverwrist.venice.db.*; +import com.silverwrist.venice.htmlcheck.*; import com.silverwrist.venice.security.AuditRecord; import com.silverwrist.venice.core.*; @@ -109,11 +110,13 @@ class TopicUserContextImpl implements TopicContext private static ResultSet queryByTopic(Statement stmt, int topicid, int uid) throws SQLException { StringBuffer sql = - new StringBuffer("SELECT t.topicid, t.num, t.creator_uid, t.top_message, t.frozen, t.archived, " - + "t.createdate, t.lastupdate, t.name, IFNULL(s.hidden,0) AS hidden, " - + "(t.top_message - IFNULL(s.last_message,-1)) AS unread FROM topics t " - + "LEFT JOIN topicsettings s ON t.topicid = s.topicid AND s.uid = "); - sql.append(uid).append(" WHERE t.topicid = ").append(topicid).append(';'); + new StringBuffer("SELECT topics.topicid, topics.num, topics.creator_uid, topics.top_message, " + + "topics.frozen, topics.archived, topics.createdate, topics.lastupdate, " + + "topics.name, IFNULL(topicsettings.hidden,0) AS hidden, " + + "(topics.top_message - IFNULL(topicsettings.last_message,-1)) AS unread " + + "FROM topics LEFT JOIN topicsettings ON topics.topicid = topicsettings.topicid " + + "AND topicsettings.uid = "); + sql.append(uid).append(" WHERE topics.topicid = ").append(topicid).append(';'); if (logger.isDebugEnabled()) logger.debug("SQL: " + sql.toString()); return stmt.executeQuery(sql.toString()); @@ -134,8 +137,29 @@ class TopicUserContextImpl implements TopicContext } // end if + private void refresh(Connection conn) throws SQLException + { + Statement stmt = conn.createStatement(); + + // perform a requery of the database + ResultSet rs = queryByTopic(stmt,topicid,conf.realUID()); + if (rs.next()) + { // update the fields that are capable of changing + top_message = rs.getInt(4); + frozen = rs.getBoolean(5); + archived = rs.getBoolean(6); + lastupdate = SQLUtil.getFullDateTime(rs,8); + hidden = rs.getBoolean(10); + unread = rs.getInt(11); + + } // end if + else // this topic must have been deleted - fsck it + makeDeleted(); + + } // end refresh + /*-------------------------------------------------------------------------------- - * Implementatuions from interface TopicContext + * Implementations from interface TopicContext *-------------------------------------------------------------------------------- */ @@ -148,22 +172,7 @@ class TopicUserContextImpl implements TopicContext try { // get a database connection conn = datapool.getConnection(); - Statement stmt = conn.createStatement(); - - // perform a requery of the database - ResultSet rs = queryByTopic(stmt,topicid,conf.realUID()); - if (rs.next()) - { // update the fields that are capable of changing - top_message = rs.getInt(4); - frozen = rs.getBoolean(5); - archived = rs.getBoolean(6); - lastupdate = SQLUtil.getFullDateTime(rs,8); - hidden = rs.getBoolean(10); - unread = rs.getInt(11); - - } // end if - else // this topic must have been deleted - fsck it - makeDeleted(); + refresh(conn); } // end try catch (SQLException e) @@ -524,8 +533,8 @@ class TopicUserContextImpl implements TopicContext } // end try catch (SQLException e) { // turn SQLException into data exception - logger.error("DB error setting topic data: " + e.getMessage(),e); - throw new DataException("unable to set topic hidden status: " + e.getMessage(),e); + logger.error("DB error setting topic user data: " + e.getMessage(),e); + throw new DataException("unable to set unread messages: " + e.getMessage(),e); } // end catch finally @@ -543,6 +552,213 @@ class TopicUserContextImpl implements TopicContext } // end fixSeen + public List getMessages(int low, int high) throws DataException, AccessError + { + if (!(conf.userCanRead())) + { // they can't read messages in this topic! + logger.error("trying to read postings w/o permission!"); + throw new AccessError("You do not have permission to read messages in this conference."); + + } // end if + + // reorder parameters so they come in the correct order! + if (low<=high) + return TopicMessageUserContextImpl.loadMessageRange(engine,conf,datapool,topicid,low,high); + else + return TopicMessageUserContextImpl.loadMessageRange(engine,conf,datapool,topicid,high,low); + + } // end getMessages + + public TopicMessageContext postNewMessage(long parent, String pseud, String text) + throws DataException, AccessError + { + if (!(conf.userCanPost())) + { // they can't post in this topic! + logger.error("trying to post w/o permission!"); + throw new AccessError("You do not have permission to post messages in this conference."); + + } // end if + + if (deleted) + { // the topic has been deleted + logger.error("can't post to a deleted topic!"); + throw new AccessError("You cannot post to a topic that has been deleted."); + + } // end if + + if (frozen && !(conf.userCanHide())) + { // can't post to a frozen topic! + logger.error("can't post to a frozen topic!"); + throw new AccessError("The topic is frozen, and you do not have permission to post to it."); + + } // end if + + if (archived && !(conf.userCanHide())) + { // can't post to a frozen topic! + logger.error("can't post to an archived topic!"); + throw new AccessError("The topic is archived, and you do not have permission to post to it."); + + } // end if + + // preprocess the two arguments through HTML checkers + HTMLChecker pseud_ch = engine.createCheckerObject(engine.HTMLC_POST_PSEUD); + HTMLChecker text_ch = engine.createCheckerObject(engine.HTMLC_POST_BODY); + try + { // run both arguments through the HTML checker + pseud_ch.append(pseud); + pseud_ch.finish(); + text_ch.append(text); + text_ch.finish(); + + } // end try + catch (AlreadyFinishedException e) + { // this isn't right... + throw new InternalStateError("HTMLChecker erroneously throwing AlreadyFinishedException",e); + + } // end catch + + String real_pseud, real_text; + int text_linecount; + try + { // retrieve the processed values + real_pseud = pseud_ch.getValue(); + real_text = text_ch.getValue(); + text_linecount = text_ch.getLines(); + + } // end try + catch (NotYetFinishedException e) + { // this isn't right either! + throw new InternalStateError("HTMLChecker erroneously throwing NotYetFinishedException",e); + + } // end catch + + int new_post_num; + long new_post_id; + java.util.Date posted_date; + Connection conn = null; + AuditRecord ar = null; + + try + { // get a database connection + conn = datapool.getConnection(); + Statement stmt = conn.createStatement(); + + // slap a lock on all the tables we need to touch + stmt.executeUpdate("LOCK TABLES confs WRITE, topics WRITE, posts WRITE, postdata WRITE, " + + "confsettings WRITE, topicsettings READ;"); + + try + { // refresh our current status and recheck allowed status + refresh(conn); + if (deleted) + { // the topic has been deleted + logger.error("can't post to a deleted topic!"); + throw new AccessError("You cannot post to a topic that has been deleted."); + + } // end if + + if (frozen && !(conf.userCanHide())) + { // can't post to a frozen topic! + logger.error("can't post to a frozen topic!"); + throw new AccessError("The topic is frozen, and you do not have permission to post to it."); + + } // end if + + if (archived && !(conf.userCanHide())) + { // can't post to a frozen topic! + logger.error("can't post to an archived topic!"); + throw new AccessError("The topic is archived, and you do not have permission to post to it."); + + } // end if + + // Determine what the new post number is. + new_post_num = top_message + 1; + + // Add the post "header" to the posts table. + StringBuffer sql = new StringBuffer("INSERT INTO posts (parent, topicid, num, linecount, creator_uid, " + + "posted, pseud) VALUES ("); + sql.append(parent).append(", ").append(topicid).append(", ").append(new_post_num).append(", "); + sql.append(text_linecount).append(", ").append(conf.realUID()).append(", '"); + posted_date = new java.util.Date(); + sql.append(SQLUtil.encodeDate(posted_date)).append("', '").append(real_pseud).append("');"); + if (logger.isDebugEnabled()) + logger.debug("SQL: " + sql.toString()); + stmt.executeUpdate(sql.toString()); + + // Retrieve the new post ID. + ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID();"); + if (!(rs.next())) + throw new InternalStateError("postMessage(): Unable to get new post ID!"); + new_post_id = rs.getLong(1); + + // Touch the topic values to reflect the added post. + sql.setLength(0); + sql.append("UPDATE topics SET top_message = ").append(new_post_num).append(", lastupdate = '"); + sql.append(SQLUtil.encodeDate(posted_date)).append("' WHERE topicid = ").append(topicid).append(';'); + if (logger.isDebugEnabled()) + logger.debug("SQL: " + sql.toString()); + stmt.executeUpdate(sql.toString()); + + // insert the post data + sql.setLength(0); + sql.append("INSERT INTO postdata (postid, data) VALUES (").append(new_post_id).append(", '"); + sql.append(real_text).append("');"); + stmt.executeUpdate(sql.toString()); + + // mark that we posted to the conference + conf.touchUpdate(conn,posted_date); + conf.touchPost(conn,posted_date); + + // fill in our own local variables to reflect the update + top_message = new_post_num; + lastupdate = posted_date; + + } // end try + finally + { // make sure we unlock the tables when we're done + Statement ulk_stmt = conn.createStatement(); + ulk_stmt.executeUpdate("UNLOCK TABLES;"); + + } // end finally + + // record what we did in an audit record + ar = new AuditRecord(AuditRecord.POST_MESSAGE,conf.realUID(),conf.userRemoteAddress(), + conf.realSIGID(),"conf=" + String.valueOf(conf.realConfID()) + ",topic=" + + String.valueOf(topicid) + ",post=" + String.valueOf(new_post_id), + "pseud=" + real_pseud); + + } // end try + catch (SQLException e) + { // turn SQLException into data exception + logger.error("DB error posting message: " + e.getMessage(),e); + throw new DataException("unable to post message: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + try + { // save off the audit record before we go, though + if ((ar!=null) && (conn!=null)) + ar.store(conn); + + } // end try + catch (SQLException e) + { // we couldn't store the audit record! + logger.error("DB error saving audit record: " + e.getMessage(),e); + + } // end catch + + if (conn!=null) + datapool.releaseConnection(conn); + + } // end finally + + // return the new message context + return new TopicMessageUserContextImpl(engine,conf,datapool,new_post_id,parent,new_post_num, + text_linecount,conf.realUID(),posted_date,real_pseud); + + } // end postMessage + /*-------------------------------------------------------------------------------- * External operations usable only from within the package *-------------------------------------------------------------------------------- diff --git a/src/com/silverwrist/venice/htmlcheck/dict/DictNode.java b/src/com/silverwrist/venice/htmlcheck/dict/DictNode.java index 5cb05e2..b27642e 100644 --- a/src/com/silverwrist/venice/htmlcheck/dict/DictNode.java +++ b/src/com/silverwrist/venice/htmlcheck/dict/DictNode.java @@ -25,7 +25,7 @@ class DictNode */ private static final int[] permute_tab = - { 7, 21, 12, 9, 1, 13, 18, 10, 5, 25, 23, 11, 16, 3, 6, 14, 24, 4, 8, 2, 15, 20, 19, 22, 17, 26 }; + { 8, 22, 13, 10, 2, 14, 19, 11, 6, 26, 24, 12, 17, 4, 7, 15, 25, 5, 9, 3, 16, 21, 20, 23, 18, 27 }; /*-------------------------------------------------------------------------------- * Attributes @@ -53,6 +53,8 @@ class DictNode { if (ltr=='-') return 0; + else if (ltr=='\'') + return 1; else return permute_tab[ltr - 'a']; diff --git a/src/com/silverwrist/venice/htmlcheck/dict/TreeLexicon.java b/src/com/silverwrist/venice/htmlcheck/dict/TreeLexicon.java index 4ee9044..13e057c 100644 --- a/src/com/silverwrist/venice/htmlcheck/dict/TreeLexicon.java +++ b/src/com/silverwrist/venice/htmlcheck/dict/TreeLexicon.java @@ -41,6 +41,62 @@ public class TreeLexicon implements ModSpellingDictionary } // end constructor + /*-------------------------------------------------------------------------------- + * Internal functions + *-------------------------------------------------------------------------------- + */ + + private boolean checkSimple(String word) + { + DictNode cur = root; + for (int i=0; i'z')) && (ch!='-') && (ch!='\'')) + return false; + cur = cur.getRef(ch); + if (cur==null) + return false; + + } // end for + + return cur.isTerminal(); + + } // end checkSimple + + private boolean checkHyphenates(String word) + { + if (word.indexOf('-')<0) + return false; // non-hyphenated + + int st = 0; + int p; + boolean ok = true; + do + { // break the word up into hyphenated compounds + p = word.indexOf('-',st); + String frag; + if (p>=0) + { // get the middle fragment of the word + frag = word.substring(st,p); + st = p + 1; + + } // end if + else // get it from the end + frag = word.substring(st); + + // check this fragment... + if (frag.length()<=1) + ok = true; // fragments of length 0 or 1 are always OK + else // anything else goes through checkSimple + ok = checkSimple(frag); + + } while (ok && (p>=0)); + + return ok; + + } // end checkHyphenates + /*-------------------------------------------------------------------------------- * Implementations from interface SpellingDictionary *-------------------------------------------------------------------------------- @@ -54,20 +110,22 @@ public class TreeLexicon implements ModSpellingDictionary public synchronized boolean checkWord(String word) { + if (word.length()<=1) + return true; // words of length 1 get a free pass String real_word = word.toLowerCase(); - DictNode cur = root; - for (int i=0; i'z')) && (ch!='-')) - return false; - cur = cur.getRef(ch); - if (cur==null) - return false; + if (checkSimple(real_word)) + return true; // word is in lexicon - we're OK to go - } // end for + if ((real_word.indexOf('\'')==(real_word.length()-2)) && (real_word.charAt(real_word.length()-1)=='s')) + { // drop the apostrophe-s from the end of the word and retry + String base = real_word.substring(0,real_word.length()-2); + if (checkSimple(base)) + return true; + return checkHyphenates(base); - return cur.isTerminal(); + } // end if + else // try hyphenated forms + return checkHyphenates(real_word); } // end checkWord @@ -83,7 +141,7 @@ public class TreeLexicon implements ModSpellingDictionary for (int i=0; i'z')) && (ch!='-')) + if (((ch<'a') || (ch>'z')) && (ch!='-') && (ch!='\'')) return; // advance down through trie diff --git a/src/com/silverwrist/venice/htmlcheck/impl/HTMLCheckerImpl.java b/src/com/silverwrist/venice/htmlcheck/impl/HTMLCheckerImpl.java index c0b4575..3a667ec 100644 --- a/src/com/silverwrist/venice/htmlcheck/impl/HTMLCheckerImpl.java +++ b/src/com/silverwrist/venice/htmlcheck/impl/HTMLCheckerImpl.java @@ -79,6 +79,13 @@ class HTMLCheckerImpl implements HTMLChecker, HTMLCheckerBackend, RewriterServic private static final short ST_PAREN = 4; private static final short ST_TAGQUOTE = 5; + /*-------------------------------------------------------------------------------- + * Internal constants + *-------------------------------------------------------------------------------- + */ + + private static final int MARGIN_SLOP = 5; + /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- @@ -127,17 +134,17 @@ class HTMLCheckerImpl implements HTMLChecker, HTMLCheckerBackend, RewriterServic private static final boolean isWordChar(char ch) { - return (Character.isUpperCase(ch) || Character.isLowerCase(ch) || (ch=='-')); + return (Character.isUpperCase(ch) || Character.isLowerCase(ch) || (ch=='-') || (ch=='\'')); } // end isWordChar - private static final int getRunLength(StringBuffer buf) + private static final int getRunLength(StringBuffer buf, int start) { - boolean word_char = isWordChar(buf.charAt(0)); + boolean word_char = isWordChar(buf.charAt(start)); int l = 1; - while (l0) { // find the length of the initial string of word or non-word characters int sublen = getRunLength(temp_buffer); if (isWordChar(temp_buffer.charAt(0))) - { // we need to check the word...but first, we must eliminate leading hyphens + { // we need to check the word...but first, we must eliminate leading hyphens and apostrophes int hyph_count = 0; - while ((hyph_count=0)) hyph_count++; emitFromStartOfTempBuffer(hyph_count); sublen -= hyph_count; - // now determine how many hyphens there are at the end of the word... + // now determine how many hyphens/apostrophes there are at the end of the word... int word_len = sublen; hyph_count = 0; - while ((word_len>0) && (temp_buffer.charAt(word_len-1)=='-')) - { // decrement word length, increment hyphen count + while ((word_len>0) && (hyph_apos.indexOf(temp_buffer.charAt(word_len-1))>=0)) + { // decrement word length, increment hyphen/apostrophe count hyph_count++; word_len--; @@ -458,13 +481,39 @@ class HTMLCheckerImpl implements HTMLChecker, HTMLCheckerBackend, RewriterServic } // end if - // now emit the rest of the hyphens + // now emit the rest of the hyphens/apostrophes emitFromStartOfTempBuffer(hyph_count); } // end if - else // just emit this many characters, line-breaking where required + else + { // just emit this many characters, line-breaking where required + if ((sublen==temp_buffer.length()) && !first && (sublen<=MARGIN_SLOP)) + { // This is intended to handle a small run of non-word characters at the end of a string (i.e. + // followed by whitespace) that should stay on the same line with its preceding word, to + // eliminate "funnies" in punctuation formatting. + emitString(temp_buffer.toString(),config.getOutputFilters(),true); + temp_buffer.setLength(0); + break; + + } // end if + + // This is kind of the inverse of the above check; if we have a small run of non-word + // characters at the START of a word (preceded by whitespace and followed by at least + // one word character), then ensure that we can keep that word and its prefixing non-word + // characters on the same line (again, avoiding "funnies" in formatting). + if ((sublen=20)) // TODO: configurable + first = last - 20; + else + first = last - (ur+2); + last--; + + } // end if + else + { // we have at least one parameter... + try + { // convert it to an integer and range-limit it + first = Integer.parseInt(foo); + if (first<0) + first = 0; + else if (first>=topic.getTotalMessages()) + first = topic.getTotalMessages() - 1; + + } // end try + catch (NumberFormatException nfe) + { // we could not translate the parameter to a number + throw new ValidationException("Message parameter is invalid."); + + } // end catch + + foo = request.getParameter("p2"); + if (StringUtil.isStringEmpty(foo)) + last = first; // just specify ONE post... + else + { // OK, we have an actual "last message" parameter... + try + { // convert it to an integer and range-limit it + last = Integer.parseInt(foo); + if ((last<0) || (last>=topic.getTotalMessages())) + last = topic.getTotalMessages() - 1; + + } // end try + catch (NumberFormatException nfe) + { // we could not translate the parameter to a number + throw new ValidationException("Message parameter is invalid."); + + } // end catch + + } // end else + + } // end else + + return new PostInterval(first,last); + + } // end getInterval + /*-------------------------------------------------------------------------------- * Overrides from class HttpServlet *-------------------------------------------------------------------------------- @@ -287,7 +428,39 @@ public class ConfDisplay extends VeniceServlet if (logger.isDebugEnabled()) logger.debug("MODE: display messages in topic"); - // TODO: handle this somehow + try + { // determine what the post interval is we want to display + PostInterval piv = getInterval(request,topic); + boolean read_new = !(StringUtil.isStringEmpty(request.getParameter("rnm"))); + boolean show_adv = !(StringUtil.isStringEmpty(request.getParameter("shac"))); + + // create the post display + TopicPosts tpos = new TopicPosts(request,sig,conf,topic,piv.getFirst(),piv.getLast(), + read_new,show_adv); + content = tpos; + page_title = topic.getName() + ": " + String.valueOf(topic.getTotalMessages()) + " Total; " + + String.valueOf(tpos.getNewMessages()) + " New; Last: " + + rdat.formatDateForDisplay(topic.getLastUpdateDate()); + + } // end try + catch (ValidationException ve) + { // there's an error in the parameters somewhere + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),"top"); + + } // end catch + catch (DataException de) + { // there was a database error retrieving topics + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error listing messages: " + de.getMessage(),"top"); + + } // end catch + catch (AccessError ae) + { // we were unable to retrieve the topic list + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),"top"); + + } // end catch } // end if else diff --git a/src/com/silverwrist/venice/servlets/PostMessage.java b/src/com/silverwrist/venice/servlets/PostMessage.java new file mode 100644 index 0000000..7e73f83 --- /dev/null +++ b/src/com/silverwrist/venice/servlets/PostMessage.java @@ -0,0 +1,414 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.servlets; + +import java.io.*; +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import org.apache.log4j.*; +import com.silverwrist.util.StringUtil; +import com.silverwrist.venice.ValidationException; +import com.silverwrist.venice.core.*; +import com.silverwrist.venice.servlets.format.*; + +public class PostMessage extends VeniceServlet +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Category logger = Category.getInstance(PostMessage.class.getName()); + + /*-------------------------------------------------------------------------------- + * Internal functions + *-------------------------------------------------------------------------------- + */ + + private static SIGContext getSIGParameter(ServletRequest request, UserContext user) + throws ValidationException, DataException + { + String str = request.getParameter("sig"); + if (str==null) + { // no SIG parameter - bail out now! + logger.error("SIG parameter not specified!"); + throw new ValidationException("No SIG specified."); + + } // end if + + try + { // turn the string into a SIGID, and thence to a SIGContext + int sigid = Integer.parseInt(str); + return user.getSIGContext(sigid); + + } // end try + catch (NumberFormatException nfe) + { // error in Integer.parseInt + logger.error("Cannot convert SIG parameter '" + str + "'!"); + throw new ValidationException("Invalid SIG parameter."); + + } // end catch + + } // end getSIGParameter + + private static ConferenceContext getConferenceParameter(ServletRequest request, SIGContext sig) + throws ValidationException, DataException, AccessError + { + String str = request.getParameter("conf"); + if (str==null) + { // no conference parameter - bail out now! + logger.error("Conference parameter not specified!"); + throw new ValidationException("No conference specified."); + + } // end if + + try + { // turn the string into a ConfID, and thence to a ConferenceContext + int confid = Integer.parseInt(str); + return sig.getConferenceContext(confid); + + } // end try + catch (NumberFormatException nfe) + { // error in Integer.parseInt + logger.error("Cannot convert conference parameter '" + str + "'!"); + throw new ValidationException("Invalid conference parameter."); + + } // end catch + + } // end getConferenceParameter + + private static TopicContext getTopicParameter(ServletRequest request, ConferenceContext conf) + throws ValidationException, DataException, AccessError + { + String str = request.getParameter("top"); + if (StringUtil.isStringEmpty(str)) + { // no topic parameter - bail out now! + logger.error("Topic parameter not specified!"); + throw new ValidationException("No topic specified."); + + } // end if + + try + { // turn the string into a TopicID, and thence to a TopicContext + short topicid = Short.parseShort(str); + return conf.getTopic(topicid); + + } // end try + catch (NumberFormatException nfe) + { // error in Integer.parseInt + logger.error("Cannot convert topic parameter '" + str + "'!"); + throw new ValidationException("Invalid topic parameter."); + + } // end catch + + } // end getTopicParameter + + private static int getPostNumber(ServletRequest request) throws ValidationException + { + String str = request.getParameter("sd"); + if (StringUtil.isStringEmpty(str)) + throw new ValidationException("Invalid parameter."); + try + { // get the number of posts we think he topic has + return Integer.parseInt(str); + + } // end try + catch (NumberFormatException nfe) + { // not a good integer... + throw new ValidationException("Invalid parameter."); + + } // end catch + + } // end getPostNumber + + /*-------------------------------------------------------------------------------- + * Overrides from class HttpServlet + *-------------------------------------------------------------------------------- + */ + + public String getServletInfo() + { + String rc = "PostMessage servlet - Handles posting messages to a conference\n" + + "Part of the Venice Web Communities System\n"; + return rc; + + } // end getServletInfo + + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + UserContext user = getUserContext(request); + RenderData rdat = createRenderData(request,response); + String page_title = null; + Object content = null; + SIGContext sig = null; // SIG context + ConferenceContext conf = null; // conference context + TopicContext topic = null; // topic context + + try + { // this outer try is to catch ValidationException + try + { // all commands require a SIG parameter + sig = getSIGParameter(request,user); + changeMenuSIG(request,sig); + if (logger.isDebugEnabled()) + logger.debug("found SIG #" + String.valueOf(sig.getSIGID())); + + } // end try + catch (DataException de) + { // error looking up the SIG + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding SIG: " + de.getMessage(),"top"); + + } // end catch + + if (content==null) + { // we got the SIG parameter OK + try + { // all commands require a conference parameter + conf = getConferenceParameter(request,sig); + if (logger.isDebugEnabled()) + logger.debug("found conf #" + String.valueOf(conf.getConfID())); + + } // end try + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding conference: " + de.getMessage(),"top"); + + } // end catch + + } // end if + + if (content==null) + { // we got the conference parameter OK + try + { // now we need a topic parameter + topic = getTopicParameter(request,conf); + if (logger.isDebugEnabled()) + logger.debug("found topic #" + String.valueOf(topic.getTopicID())); + + } // end try + catch (DataException de) + { // error looking up the conference + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error finding topic: " + de.getMessage(),"top"); + + } // end catch + + } // end if + + } // end try + catch (ValidationException ve) + { // these all get handled in pretty much the same way + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),"top"); + + } // end catch + catch (AccessError ae) + { // these all get handled in pretty much the same way + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),"top"); + + } // end catch + + if (content==null) + { // make sure we've got some post data + String raw_postdata = request.getParameter("pb"); + if (StringUtil.isStringEmpty(raw_postdata)) + { // don't allow zero-size posts + rdat.nullResponse(); + return; + + } // end if + + final String yes = "Y"; + + // now decide what to do based on which button got clicked + if (isImageButtonClicked(request,"cancel")) + { // canceled posting - take us back to familiar ground + rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" + + String.valueOf(conf.getConfID()) + "&top=" + + String.valueOf(topic.getTopicNumber())); + return; + + } // end if ("Cancel") + else if (isImageButtonClicked(request,"preview")) + { // previewing the post! + try + { // generate a preview view + content = new PostPreview(getVeniceEngine(),sig,conf,topic,request.getParameter("pseud"), + raw_postdata,request.getParameter("next"),getPostNumber(request), + yes.equals(request.getParameter("attach"))); + page_title = "Previewing Post"; + + } // end try + catch (ValidationException ve) + { // there was some sort of a parameter error in the display (getPostNumber can throw this) + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),"top"); + + } // end catch + + } // end else if ("Preview & Spellcheck") + else if (isImageButtonClicked(request,"post")) + { // post the message, and then reload the same topic + try + { // first, check against slippage + int pn = getPostNumber(request); + if (pn==topic.getTotalMessages()) + { // no slippage - post the message!!! + TopicMessageContext msg = topic.postNewMessage(0,request.getParameter("pseud"),raw_postdata); + if (yes.equals(request.getParameter("attach"))) + { // we have an attachment to upload... + // TODO: do something to upload the attachment + rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" + + String.valueOf(conf.getConfID()) + "&top=" + + String.valueOf(topic.getTopicNumber()) + "&rnm=1"); + return; + + } // end if + else + { // no attachment - jump back to the topic + rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" + + String.valueOf(conf.getConfID()) + "&top=" + + String.valueOf(topic.getTopicNumber()) + "&rnm=1"); + return; + + } // end else + + } // end if + else + { // slippage detected - show the slippage display + content = new PostSlippage(getVeniceEngine(),sig,conf,topic,pn,request.getParameter("next"), + request.getParameter("pseud"),raw_postdata, + yes.equals(request.getParameter("attach"))); + page_title = "Slippage or Double-Click Detected"; + + } // end else + + } // end try + catch (ValidationException ve) + { // there was some sort of a parameter error in the display + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),"top"); + + } // end catch + catch (DataException de) + { // there was a database error posting the message + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error posting message: " + de.getMessage(),"top"); + + } // end catch + catch (AccessError ae) + { // we were unable to retrieve the topic list + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),"top"); + + } // end catch + + } // end else if ("Post & Reload") + else if (isImageButtonClicked(request,"postnext")) + { // post the message, and then go to the "next" topic + try + { // first, check against slippage + int pn = getPostNumber(request); + if (pn==topic.getTotalMessages()) + { // no slippage - post the message! + TopicMessageContext msg = topic.postNewMessage(0,request.getParameter("pseud"),raw_postdata); + + short next; + try + { // attempt to get the value of the "next topic" parameter + String foo = request.getParameter("next"); + if (StringUtil.isStringEmpty(foo)) + next = topic.getTopicNumber(); + else + next = Short.parseShort(foo); + + } // end try + catch (NumberFormatException nfe) + { // just default me + next = topic.getTopicNumber(); + + } // end catch + + if (yes.equals(request.getParameter("attach"))) + { // we have an attachment to upload... + // TODO: jump somewhere we can upload the attachment! + rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" + + String.valueOf(conf.getConfID()) + "&top=" + String.valueOf(next) + "&rnm=1"); + return; + + } // end if + else + { // no attachment - jump to the next topic + rdat.redirectTo("confdisp?sig=" + String.valueOf(sig.getSIGID()) + "&conf=" + + String.valueOf(conf.getConfID()) + "&top=" + String.valueOf(next) + "&rnm=1"); + return; + + } // end else + + } // end if + else + { // slippage detected - show the slippage display + content = new PostSlippage(getVeniceEngine(),sig,conf,topic,pn,request.getParameter("next"), + request.getParameter("pseud"),raw_postdata, + yes.equals(request.getParameter("attach"))); + page_title = "Slippage or Double-Click Detected"; + + } // end else + + } // end try + catch (ValidationException ve) + { // there was some sort of a parameter error in the display + page_title = "Error"; + content = new ErrorBox(null,ve.getMessage(),"top"); + + } // end catch + catch (DataException de) + { // there was a database error posting the message + page_title = "Database Error"; + content = new ErrorBox(page_title,"Database error posting message: " + de.getMessage(),"top"); + + } // end catch + catch (AccessError ae) + { // we were unable to retrieve the topic list + page_title = "Access Error"; + content = new ErrorBox(page_title,ae.getMessage(),"top"); + + } // end catch + + } // end else if ("Post & Go Next") + else + { // unknown button clicked + page_title = "Internal Error"; + logger.error("no known button click on PostMessage.doPost"); + content = new ErrorBox(page_title,"Unknown command button pressed","top"); + + } // end else + + } // end if (got all parameters oK) + + BaseJSPData basedat = new BaseJSPData(page_title,"post",content); + basedat.transfer(getServletContext(),rdat); + + } // end doPost + +} // end class PostMessage diff --git a/src/com/silverwrist/venice/servlets/format/PostPreview.java b/src/com/silverwrist/venice/servlets/format/PostPreview.java new file mode 100644 index 0000000..92e1984 --- /dev/null +++ b/src/com/silverwrist/venice/servlets/format/PostPreview.java @@ -0,0 +1,190 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.servlets.format; + +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import com.silverwrist.util.StringUtil; +import com.silverwrist.venice.htmlcheck.*; +import com.silverwrist.venice.core.*; + +public class PostPreview implements JSPRender +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + // Attribute name for request attribute + protected static final String ATTR_NAME = "com.silverwrist.venice.content.PostPreview"; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private SIGContext sig; + private ConferenceContext conf; + private TopicContext topic; + private String next; + private String pseud; + private String data; + private String preview; + private int num_errors; + private int msgs; + private boolean attach; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public PostPreview(VeniceEngine engine, SIGContext sig, ConferenceContext conf, TopicContext topic, + String pseud, String data, String next, int msgs, boolean attach) + { + this.sig = sig; + this.conf = conf; + this.topic = topic; + this.next = next; + this.msgs = msgs; + this.attach = attach; + + try + { // sanitize the pseud data + HTMLChecker check = engine.getEscapingChecker(); + check.append(pseud); + check.finish(); + this.pseud = check.getValue(); + + // sanitize the post box data + check.reset(); + check.append(data); + check.finish(); + this.data = check.getValue(); + + // now generate the preview + check = engine.getPreviewChecker(); + check.append(data); + check.finish(); + this.preview = check.getValue(); + this.num_errors = check.getCounter("spelling"); + + } // end try + catch (HTMLCheckerException e) + { // this is a bad issue... + throw new InternalStateError("spurious HTMLCheckerException thrown"); + + } // end catch + + } // end constructor + + /*-------------------------------------------------------------------------------- + * External static functions + *-------------------------------------------------------------------------------- + */ + + public static PostPreview retrieve(ServletRequest request) + { + return (PostPreview)(request.getAttribute(ATTR_NAME)); + + } // end retrieve + + /*-------------------------------------------------------------------------------- + * Implementations from interface JSPRender + *-------------------------------------------------------------------------------- + */ + + public void store(ServletRequest request) + { + request.setAttribute(ATTR_NAME,this); + + } // end store + + public String getTargetJSPName() + { + return "preview.jsp"; + + } // end getTargetJSPName + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + public int getNumSpellingErrors() + { + return num_errors; + + } // end getNumSpellingErrors + + public String getPreviewData() + { + return preview; + + } // end getPreviewData + + public int getSIGID() + { + return sig.getSIGID(); + + } // end getSIGID + + public int getConfID() + { + return conf.getConfID(); + + } // end getConfID + + public int getTopicNumber() + { + return topic.getTopicNumber(); + + } // end getTopicNumber + + public int getTotalMessages() + { + return msgs; + + } // end getTotalMessages + + public String getNextVal() + { + return next; + + } // end getNextVal + + public String getPseud() + { + return pseud; + + } // end getPseud + + public boolean attachChecked() + { + return attach; + + } // end attachChecked + + public String getBodyText() + { + return data; + + } // end getBodyText + +} // end class PostPreview diff --git a/src/com/silverwrist/venice/servlets/format/PostSlippage.java b/src/com/silverwrist/venice/servlets/format/PostSlippage.java new file mode 100644 index 0000000..3b6fa18 --- /dev/null +++ b/src/com/silverwrist/venice/servlets/format/PostSlippage.java @@ -0,0 +1,216 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.servlets.format; + +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import com.silverwrist.util.StringUtil; +import com.silverwrist.venice.htmlcheck.*; +import com.silverwrist.venice.core.*; + +public class PostSlippage implements JSPRender +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + // Attribute name for request attribute + protected static final String ATTR_NAME = "com.silverwrist.venice.content.PostSlippage"; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private SIGContext sig; + private ConferenceContext conf; + private TopicContext topic; + private List messages; + private String next; + private String pseud; + private String text; + private boolean attach; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public PostSlippage(VeniceEngine engine, SIGContext sig, ConferenceContext conf, TopicContext topic, + int lastval, String next, String pseud, String text, boolean attach) + throws DataException, AccessError + { + this.sig = sig; + this.conf = conf; + this.topic = topic; + this.messages = topic.getMessages(lastval,topic.getTotalMessages()-1); + this.next = next; + this.attach = attach; + + try + { // run the text and pseud through an HTML checker to escape them + HTMLChecker ch = engine.getEscapingChecker(); + ch.append(pseud); + ch.finish(); + this.pseud = ch.getValue(); + ch.reset(); + ch.append(text); + ch.finish(); + this.text = text; + + } // end try + catch (HTMLCheckerException e) + { // this shouldn't happen + throw new InternalStateError("spurious HTMLCheckerException thrown"); + + } // end catch + + } // end constructor + + /*-------------------------------------------------------------------------------- + * External static functions + *-------------------------------------------------------------------------------- + */ + + public static PostSlippage retrieve(ServletRequest request) + { + return (PostSlippage)(request.getAttribute(ATTR_NAME)); + + } // end retrieve + + /*-------------------------------------------------------------------------------- + * Implementations from interface JSPRender + *-------------------------------------------------------------------------------- + */ + + public void store(ServletRequest request) + { + request.setAttribute(ATTR_NAME,this); + + } // end store + + public String getTargetJSPName() + { + return "slippage.jsp"; + + } // end getTargetJSPName + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + public static String getPosterName(TopicMessageContext msg) + { + try + { // have to guard agains a DataException here + return msg.getCreatorName(); + + } // end try + catch (DataException de) + { // just return "unknown" on failure + return "(unknown)"; + + } // end catch + + } // end getPosterName + + public static String getMessageBodyText(TopicMessageContext msg) + { + try + { // have to guard against a DataException here + return msg.getBodyText(); + + } // end try + catch (DataException de) + { // just return an error message + return "(Unable to retrieve message data: " + StringUtil.encodeHTML(de.getMessage()) + ")"; + + } // end catch + + } // end getMessageBodyText + + public int getSIGID() + { + return sig.getSIGID(); + + } // end getSIGID + + public int getConfID() + { + return conf.getConfID(); + + } // end getConfID + + public int getTopicNumber() + { + return topic.getTopicNumber(); + + } // end getTopicNumber + + public String getTopicName() + { + return topic.getName(); + + } // end getTopicName + + public String getIdentifyingData() + { + return "Slippage posting to topic " + String.valueOf(topic.getTopicID()); + + } // end getIdentifyingData + + public int getTotalMessages() + { + return topic.getTotalMessages(); + + } // end getTotalMessages + + public Iterator getMessageIterator() + { + return messages.iterator(); + + } // end getMessageIterator + + public String getNextVal() + { + return next; + + } // end getNextVal + + public String getPseud() + { + return pseud; + + } // end getPseud + + public boolean attachChecked() + { + return attach; + + } // end attachChecked + + public String getBodyText() + { + return text; + + } // end getBodyText + +} // end class PostSlippage diff --git a/src/com/silverwrist/venice/servlets/format/TopicListing.java b/src/com/silverwrist/venice/servlets/format/TopicListing.java index a258590..ab456ca 100644 --- a/src/com/silverwrist/venice/servlets/format/TopicListing.java +++ b/src/com/silverwrist/venice/servlets/format/TopicListing.java @@ -106,6 +106,14 @@ public class TopicListing implements JSPRender } // end getConfID + public String getLocator() + { + StringBuffer buf = new StringBuffer("sig="); + buf.append(sig.getSIGID()).append("&conf=").append(conf.getConfID()); + return buf.toString(); + + } // end getLocator + public String getConfName() { return conf.getName(); @@ -118,6 +126,15 @@ public class TopicListing implements JSPRender } // end canDoReadNew + public String getNextLocator() + { + StringBuffer buf = new StringBuffer("sig="); + buf.append(sig.getSIGID()).append("&conf=").append(conf.getConfID()).append("&top="); + buf.append(visit_order.getNext()); + return buf.toString(); + + } // end getNextLocator + public boolean canCreateTopic() { return conf.canCreateTopic(); diff --git a/src/com/silverwrist/venice/servlets/format/TopicPosts.java b/src/com/silverwrist/venice/servlets/format/TopicPosts.java new file mode 100644 index 0000000..6859e13 --- /dev/null +++ b/src/com/silverwrist/venice/servlets/format/TopicPosts.java @@ -0,0 +1,354 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.servlets.format; + +import java.util.*; +import javax.servlet.*; +import javax.servlet.http.*; +import com.silverwrist.util.StringUtil; +import com.silverwrist.venice.core.*; + +public class TopicPosts implements JSPRender +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + // Attribute name for request attribute + protected static final String ATTR_NAME = "com.silverwrist.venice.content.TopicPosts"; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private SIGContext sig; + private ConferenceContext conf; + private TopicContext topic; + private int first; + private int last; + private boolean show_advanced; + private int unread; + private List messages; + private TopicVisitOrder visit_order; + private String cache_locator = null; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public TopicPosts(HttpServletRequest request, SIGContext sig, ConferenceContext conf, TopicContext topic, + int first, int last, boolean read_new, boolean show_advanced) + throws DataException, AccessError + { + this.sig = sig; + this.conf = conf; + this.topic = topic; + this.first = first; + this.last = last; + this.show_advanced = show_advanced; + this.unread = topic.getUnreadMessages(); + if (read_new) + topic.setUnreadMessages(0); + this.messages = topic.getMessages(first,last); + this.visit_order = TopicVisitOrder.retrieve(request.getSession(true),conf.getConfID()); + visit_order.visit(topic.getTopicNumber()); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * External static functions + *-------------------------------------------------------------------------------- + */ + + public static TopicPosts retrieve(ServletRequest request) + { + return (TopicPosts)(request.getAttribute(ATTR_NAME)); + + } // end retrieve + + /*-------------------------------------------------------------------------------- + * Implementations from interface JSPRender + *-------------------------------------------------------------------------------- + */ + + public void store(ServletRequest request) + { + request.setAttribute(ATTR_NAME,this); + + } // end store + + public String getTargetJSPName() + { + return "posts.jsp"; + + } // end getTargetJSPName + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + public static String getPosterName(TopicMessageContext msg) + { + try + { // have to guard agains a DataException here + return msg.getCreatorName(); + + } // end try + catch (DataException de) + { // just return "unknown" on failure + return "(unknown)"; + + } // end catch + + } // end getPosterName + + public static String getMessageBodyText(TopicMessageContext msg) + { + try + { // have to guard against a DataException here + return msg.getBodyText(); + + } // end try + catch (DataException de) + { // just return an error message + return "(Unable to retrieve message data: " + StringUtil.encodeHTML(de.getMessage()) + ")"; + + } // end catch + + } // end getMessageBodyText + + public int getSIGID() + { + return sig.getSIGID(); + + } // end getSIGID + + public int getConfID() + { + return conf.getConfID(); + + } // end getConfID + + public int getTopicNumber() + { + return topic.getTopicNumber(); + + } // end getTopicNumber + + public int getNextTopicNumber() + { + return visit_order.getNext(); + + } // end getNextTopicNumber + + public String getConfLocator() + { + StringBuffer buf = new StringBuffer("sig="); + buf.append(sig.getSIGID()).append("&conf=").append(conf.getConfID()); + return buf.toString(); + + } // end getConfLocator + + public String getLocator() + { + if (cache_locator==null) + { // build up the standard locator + StringBuffer buf = new StringBuffer("sig="); + buf.append(sig.getSIGID()).append("&conf=").append(conf.getConfID()).append("&top="); + buf.append(topic.getTopicNumber()); + cache_locator = buf.toString(); + + } // end if + + return cache_locator; + + } // end getLocator + + public String getNextLocator() + { + StringBuffer buf = new StringBuffer("sig="); + buf.append(sig.getSIGID()).append("&conf=").append(conf.getConfID()).append("&top="); + buf.append(visit_order.getNext()); + return buf.toString(); + + } // end getNextLocator + + public String getIdentifyingData() + { + StringBuffer buf = new StringBuffer("Posts "); + buf.append(first).append(" through ").append(last).append(" in topic #").append(topic.getTopicID()); + return buf.toString(); + + } // end getIdentifyingData + + public String getTopicName() + { + return topic.getName(); + + } // end getTopicName + + public int getTotalMessages() + { + return topic.getTotalMessages(); + + } // end getTotalMessages + + public int getNewMessages() + { + return unread; + + } // end getNewMessages + + public Date getLastUpdate() + { + return topic.getLastUpdateDate(); + + } // end getLastUpdate + + public boolean isTopicHidden() + { + return topic.isHidden(); + + } // end isTopicHidden + + public boolean canDoNextTopic() + { + return visit_order.isNext(); + + } // end canDoNextTopic + + public boolean canFreezeTopic() + { + return topic.canFreeze(); + + } // end canFreezeTopic + + public boolean isTopicFrozen() + { + return topic.isFrozen(); + + } // end isTopicFrozen + + public boolean canArchiveTopic() + { + return topic.canArchive(); + + } // end canArchiveTopic + + public boolean isTopicArchived() + { + return topic.isArchived(); + + } // end isTopicArchived + + public boolean canDeleteTopic() + { + return false; // TODO: fix me + + } // end canDeleteTopic + + public boolean canScrollUp() + { + return (first>0); + + } // end canScrollUp + + public String getScrollUpLocator() + { + int new_first = first - 20; // TODO: configurable + int new_last = last - 1; + if (new_first<0) + { // normalize so we start at 0 + new_last += (-new_first); + new_first = 0; + + } // end if + + StringBuffer buf = new StringBuffer("p1="); + buf.append(new_first).append("&p2=").append(new_last); + return buf.toString(); + + } // end getScrollUpLocator + + public boolean canScrollDown() + { + return ((topic.getTotalMessages() - (1 + last))>=20); // TODO: configurable + + } // end canScrollDown + + public String getScrollDownLocator() + { + StringBuffer buf = new StringBuffer("p1="); + buf.append(last+1).append("&p2=").append(last+20); // TODO: configurable + return buf.toString(); + + } // end getScrollDownLocator + + public boolean canScrollToEnd() + { + return ((topic.getTotalMessages() - (1 + last))>0); + + } // end canScrollToEnd + + public String getScrollToEndLocator() + { + int my_last = topic.getTotalMessages(); + StringBuffer buf = new StringBuffer("p1="); + buf.append(my_last-20).append("&p2=").append(my_last-1); // TODO: configurable + return buf.toString(); + + } // end getScrollToEndLocator + + public Iterator getMessageIterator() + { + return messages.iterator(); + + } // end getMessageIterator() + + public boolean emitBreakLinePoint(int msg) + { + return (msg==(topic.getTotalMessages()-unread)); + + } // end emitBreakLinePoint + + public boolean showAdvanced() + { + return show_advanced && (last==first); + + } // end showAdvanced + + public boolean displayPostBox() + { + boolean flag1 = conf.canPostToConference(); + boolean flag2 = (topic.isFrozen() ? topic.canFreeze() : true); + boolean flag3 = (topic.isArchived() ? topic.canArchive() : true); + return flag1 && flag2 && flag3; + + } // end displayPostBox + + public String getDefaultPseud() + { + return conf.getDefaultPseud(); + + } // end getDefaultPseud + +} // end class TopicPosts diff --git a/web/format/posts.jsp b/web/format/posts.jsp new file mode 100644 index 0000000..f86c349 --- /dev/null +++ b/web/format/posts.jsp @@ -0,0 +1,327 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.util.StringUtil" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.servlets.Variables" %> +<%@ page import = "com.silverwrist.venice.servlets.format.*" %> +<% + TopicPosts data = TopicPosts.retrieve(request); + Variables.failIfNull(data); + RenderData rdat = RenderConfig.createRenderData(application,request,response); +%> +<% if (rdat.useHTMLComments()) { %><% } %> +<% + String tmp; + if (data.isTopicArchived()) + tmp = "(Archived) "; + else if (data.isTopicFrozen()) + tmp = "(Frozen) "; + else + tmp = ""; + rdat.writeContentHeader(out,data.getTopicName(),tmp + String.valueOf(data.getTotalMessages()) + + " Total; " + String.valueOf(data.getNewMessages()) + " New; Last: " + + rdat.formatDateForDisplay(data.getLastUpdate())); +%> + + + + + + + + + + +
    + <% if (rdat.useHTMLComments()) { %><% } %> + ">" ALT="Topic List" WIDTH=80 HEIGHT=24 + BORDER=0> +    + <% if (data.isTopicHidden()) { %> + ">" ALT="Show Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } else { %> + ">" ALT="Hide Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } // end if %> +    + <% if (data.canDoNextTopic()) { %> + ">" ALT="Next Topic" WIDTH=80 HEIGHT=24 + BORDER=0> +    + <% if (data.getNewMessages()>0) { %> + <%-- TODO: this doesn't do Keep New yet --%> + ">" ALT="Next & Keep New" WIDTH=80 HEIGHT=24 + BORDER=0> +    + <% } // end if %> + <% } // end if %> + + <% if (rdat.useHTMLComments()) { %><% } %> + <% if (data.canFreezeTopic()) { %> +    + <% if (data.isTopicFrozen()) { %> + ">" ALT="Unfreeze Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } else { %> + ">" ALT="Freeze Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } // end if %> + <% } // end if %> + <% if (data.canArchiveTopic()) { %> +    + <% if (data.isTopicArchived()) { %> + ">" ALT="Unarchive Topic" WIDTH=80 + HEIGHT=24 BORDER=0> + <% } else { %> + ">" ALT="Archive Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } // end if %> + <% } // end if %> + <% if (data.canDeleteTopic()) { %> +    + ">" ALT="Delete Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } // end if %> +
     
    + <% if (rdat.useHTMLComments()) { %><% } %> +
    "> + + + +    + " NAME="Go" ALT="Go" + ALIGN=BOTTOM WIDTH=80 HEIGHT=24 BORDER=0> +
    +
    <%= rdat.getStdFontTag(null,2) %> + <% if (rdat.useHTMLComments()) { %><% } %> + <%-- TODO: the number "20" should be configurable --%> + [  + ">View All + <% if (data.canScrollUp()) { %> +  |  + ">Scroll Up 20 + <% } // end if %> + <% if (data.canScrollDown()) { %> +  |  + ">Scroll Down 20 + <% } // end if %> + <% if (data.canScrollDown()) { %> +  |  + ">Scroll To End + <% } // end if %> +  |  + Bottom +  ] +
    + +<% if (rdat.useHTMLComments()) { %><% } %> +<% + Iterator it = data.getMessageIterator(); + String last_post = rdat.getEncodedServletPath("confdisp?" + data.getLocator() + "&shac=1&p1=" + + String.valueOf(data.getTotalMessages() - 1)); + boolean can_line = false; +%> +<% while (it.hasNext()) { %> + <% + TopicMessageContext msg = (TopicMessageContext)(it.next()); + String poster = data.getPosterName(msg); + %> + <% if (can_line && data.emitBreakLinePoint(msg.getPostNumber())) { %>
    <% } %> + <% if (data.showAdvanced()) { %> +
    +
    + <% } // end if %> + <%= rdat.getStdFontTag(null,2) %> + "><%= msg.getPostNumber() %> of + <%= data.getTotalMessages() - 1 %> + <%= rdat.getStdFontTag(null,1) %><<%= "TODO" %>>
    + <%= msg.getPseud() %> + ( + " TARGET="_blank"><%= poster %>, + <%= rdat.formatDateForDisplay(msg.getPostDate()) %> + ) + <%-- TODO: paperclip goes here if we have an attachment --%> +

    + <% if (msg.isScribbled()) { %> + + (Scribbled by <%= data.getMessageBodyText(msg) %> on + <%= rdat.formatDateForDisplay(msg.getScribbleDate()) %>) + + <% } else if (msg.isHidden() && !(data.showAdvanced())) { %> + + ">(Hidden + Message: <%= msg.getNumLines() %> <% if (msg.getNumLines()==1) { %>Line<% } else { %>Lines<% } %> + + <% } else { %> +

    <%= data.getMessageBodyText(msg) %>
    + <% } // end if %> + <% if (data.showAdvanced()) { %> +
    + <% if (!(msg.isScribbled())) { %> + <% if (msg.canHide()) { %> + <% if (msg.isHidden()) { %> + ">" ALT="Show" WIDTH=80 HEIGHT=24 BORDER=0>

    + <% } else { %> + ">" ALT="Hide" WIDTH=80 HEIGHT=24 BORDER=0>

    + <% } // end if %> + <% } // end if (can hide) %> + <% if (msg.canScribble()) { %> + ">" ALT="Scribble" WIDTH=80 HEIGHT=24 + BORDER=0>

    + <% } // end if (can scribble) %> + <% } // end if (not already scribbled) %> + <% if (msg.canNuke()) { %> + ">" ALT="Nuke" WIDTH=80 HEIGHT=24 + BORDER=0>

    + <% } // end if (can nuke) %> +


    + <% } // end if (showing advanced controls) %> + <% can_line = true; %> +<% } // end while %> +<% if (rdat.useHTMLComments()) { %><% } %> + + + + + + + + + + +
     <%= rdat.getStdFontTag(null,2) %> + <% if (rdat.useHTMLComments()) { %><% } %> + <%-- TODO: the number "20" should be configurable --%> + [  + ">View All + <% if (data.canScrollUp()) { %> +  |  + ">Scroll Up 20 + <% } // end if %> + <% if (data.canScrollDown()) { %> +  |  + ">Scroll Down 20 + <% } // end if %> + <% if (data.canScrollDown()) { %> +  |  + ">Scroll To End + <% } // end if %> +  |  + Top +  ] +
    + <% if (rdat.useHTMLComments()) { %><% } %> + ">" ALT="Topic List" WIDTH=80 HEIGHT=24 + BORDER=0> +    + <% if (data.isTopicHidden()) { %> + ">" ALT="Show Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } else { %> + ">" ALT="Hide Topic" WIDTH=80 HEIGHT=24 + BORDER=0> + <% } // end if %> +    + <% if (data.canDoNextTopic()) { %> + ">" ALT="Next Topic" WIDTH=80 HEIGHT=24 + BORDER=0> +    + <% if (data.getNewMessages()>0) { %> + <%-- TODO: this doesn't do Keep New yet --%> + ">" ALT="Next & Keep New" WIDTH=80 HEIGHT=24 + BORDER=0> +    + <% } // end if %> + <% } // end if %> +  
    + +<% if (data.displayPostBox()) { %> +
    <%= rdat.getStdFontTag(null,3) %>Post Message in "<%= data.getTopicName() %>": +
    +
    "> + + + + <% if (data.canDoNextTopic()) { %> + + <% } // end if %> + + + + + + + + + +
    + <%= rdat.getStdFontTag(null,2) %>Your name/header:
    + + <%= rdat.getStdFontTag(null,2) %> Attach a file +
    <%= rdat.getStdFontTag(null,2) %>Message:<%= rdat.getStdFontTag(null,2) %> + HTML Guide +
    + +
    + " ALT="Preview" NAME="preview" + WIDTH=80 HEIGHT=24 BORDER=0> +    + " ALT="Post & Reload" + NAME="post" WIDTH=80 HEIGHT=24 BORDER=0> + <% if (data.canDoNextTopic()) { %> +    + " ALT="Post & Go Next" + NAME="postnext" WIDTH=80 HEIGHT=24 BORDER=0> + <% } // end if %> +
    +
    +<% } else if (data.isTopicArchived()) { %> +
    <%= rdat.getStdFontTag(null,2) %>This is an Archived Topic
    +<% } else if (data.isTopicFrozen()) { %> +
    <%= rdat.getStdFontTag(null,2) %>This is a Frozen Topic
    +<% } // end if %> +<% rdat.writeFooter(out); %> diff --git a/web/format/preview.jsp b/web/format/preview.jsp new file mode 100644 index 0000000..c8c52e5 --- /dev/null +++ b/web/format/preview.jsp @@ -0,0 +1,81 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.util.StringUtil" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.servlets.Variables" %> +<%@ page import = "com.silverwrist.venice.servlets.format.*" %> +<% + PostPreview data = PostPreview.retrieve(request); + Variables.failIfNull(data); + RenderData rdat = RenderConfig.createRenderData(application,request,response); +%> +<% rdat.writeContentHeader(out,"Previewing Post",null); %> +<%= rdat.getStdFontTag(null,3) %> + <% if (data.getNumSpellingErrors()==0) { %> + Your post did not contain any spelling errors. + <% } else if (data.getNumSpellingErrors()==1) { %> + There was 1 spelling error in your post. + <% } else { %> + There were <%= data.getNumSpellingErrors() %> spelling errors in your post. + <% } // end if %> + +

    <%= data.getPreviewData() %>

    + +
    "> + + + + <% if (!(StringUtil.isStringEmpty(data.getNextVal()))) { %> + + <% } // end if %> + + + + + + + + + +
    + <%= rdat.getStdFontTag(null,2) %>Your name/header:
    + + <%= rdat.getStdFontTag(null,2) %>CHECKED<% } %> > Attach a file +
    <%= rdat.getStdFontTag(null,2) %>Message:<%= rdat.getStdFontTag(null,2) %> + HTML Guide +
    + +
    + " ALT="Preview" NAME="preview" + WIDTH=80 HEIGHT=24 BORDER=0> +    + " ALT="Post & Reload" + NAME="post" WIDTH=80 HEIGHT=24 BORDER=0> + <% if (!(StringUtil.isStringEmpty(data.getNextVal()))) { %> +    + " ALT="Post & Go Next" + NAME="postnext" WIDTH=80 HEIGHT=24 BORDER=0> + <% } // end if %> +    + " ALT="Cancel" + NAME="cancel" WIDTH=80 HEIGHT=24 BORDER=0> +
    +
    +<% rdat.writeFooter(out); %> diff --git a/web/format/slippage.jsp b/web/format/slippage.jsp new file mode 100644 index 0000000..8313042 --- /dev/null +++ b/web/format/slippage.jsp @@ -0,0 +1,107 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.util.StringUtil" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.servlets.Variables" %> +<%@ page import = "com.silverwrist.venice.servlets.format.*" %> +<% + PostSlippage data = PostSlippage.retrieve(request); + Variables.failIfNull(data); + RenderData rdat = RenderConfig.createRenderData(application,request,response); +%> +<% if (rdat.useHTMLComments()) { %><% } %> +<% rdat.writeContentHeader(out,"Slippage or Double-Click Detected",null); %> +<%= rdat.getStdFontTag(null,2) %> + The following posts slipped in while you were typing. You may choose to edit your message and + re-post, just post it as is, or cancel your posting altogether. + + +<% if (rdat.useHTMLComments()) { %><% } %> +<% Iterator it = data.getMessageIterator(); %> +<% while (it.hasNext()) { %> + <% + TopicMessageContext msg = (TopicMessageContext)(it.next()); + String poster = data.getPosterName(msg); + %> + <%= rdat.getStdFontTag(null,2) %> + <%= msg.getPostNumber() %> of <%= data.getTotalMessages() - 1 %> + <%= rdat.getStdFontTag(null,1) %><<%= "TODO" %>>
    + <%= msg.getPseud() %> + ( + " TARGET="_blank"><%= poster %>, + <%= rdat.formatDateForDisplay(msg.getPostDate()) %> + ) +

    + <% if (msg.isScribbled()) { %> + + (Scribbled by <%= data.getMessageBodyText(msg) %> on + <%= rdat.formatDateForDisplay(msg.getScribbleDate()) %>) + + <% } else { %> +

    <%= data.getMessageBodyText(msg) %>
    + <% } // end if %> +<% } // end while %> +<% if (rdat.useHTMLComments()) { %><% } %> + +
    <%= rdat.getStdFontTag(null,3) %>Post Message in "<%= data.getTopicName() %>": +
    +
    "> + + + + <% if (!(StringUtil.isStringEmpty(data.getNextVal()))) { %> + + <% } // end if %> + + + + + + + + + +
    + <%= rdat.getStdFontTag(null,2) %>Your name/header:
    + + <%= rdat.getStdFontTag(null,2) %>CHECKED<% } %> > Attach a file +
    <%= rdat.getStdFontTag(null,2) %>Message:<%= rdat.getStdFontTag(null,2) %> + HTML Guide +
    + +
    + " ALT="Preview" NAME="preview" + WIDTH=80 HEIGHT=24 BORDER=0> +    + " ALT="Post & Reload" + NAME="post" WIDTH=80 HEIGHT=24 BORDER=0> + <% if (!(StringUtil.isStringEmpty(data.getNextVal()))) { %> +    + " ALT="Post & Go Next" + NAME="postnext" WIDTH=80 HEIGHT=24 BORDER=0> + <% } // end if %> +    + " ALT="Cancel" + NAME="cancel" WIDTH=80 HEIGHT=24 BORDER=0> +
    +
    +<% rdat.writeFooter(out); %> + + diff --git a/web/format/topics.jsp b/web/format/topics.jsp index 9e3008b..bdc7ce4 100644 --- a/web/format/topics.jsp +++ b/web/format/topics.jsp @@ -24,8 +24,7 @@ TopicListing data = TopicListing.retrieve(request); Variables.failIfNull(data); RenderData rdat = RenderConfig.createRenderData(application,request,response); - String self = "confdisp?sig=" + String.valueOf(data.getSIGID()) + "&conf=" - + String.valueOf(data.getConfID()); + String self = "confdisp?" + data.getLocator(); String tmp; %> <% if (rdat.useHTMLComments()) { %><% } %> @@ -36,14 +35,14 @@ SRC="<%= rdat.getFullImagePath("bn_conference_list.gif") %>" ALT="Conference List" WIDTH=80 HEIGHT=24 BORDER=0>   <% if (data.canCreateTopic()) { %> - <% tmp = "confops?sig=" + String.valueOf(data.getSIGID()) + "&conf=" - + String.valueOf(data.getConfID()) + "&cmd=T"; %> - " - ALT="Add Topic" WIDTH=80 HEIGHT=24 BORDER=0>   + <% tmp = rdat.getEncodedServletPath("confops?" + data.getLocator() + "&cmd=T"); %> + " ALT="Add Topic" + WIDTH=80 HEIGHT=24 BORDER=0>   <% } // end if %> <% if (data.canDoReadNew()) { %> - " - ALT="Read New" WIDTH=80 HEIGHT=24 BORDER=0>   + ">" ALT="Read New" WIDTH=80 HEIGHT=24 + BORDER=0>   <% } // end if %> " ALT="Manage" WIDTH=80 HEIGHT=24 BORDER=0>   @@ -59,52 +58,61 @@ <% tmp = self + "&sort=" + String.valueOf(data.isSort(ConferenceContext.SORT_NUMBER) ? -ConferenceContext.SORT_NUMBER : ConferenceContext.SORT_NUMBER); %> - # + # <%= rdat.getStdFontTag(null,2) %> <% tmp = self + "&sort=" + String.valueOf(data.isSort(ConferenceContext.SORT_NAME) ? -ConferenceContext.SORT_NAME : ConferenceContext.SORT_NAME); %> - Topic Name + Topic Name <%= rdat.getStdFontTag(null,2) %> <% tmp = self + "&sort=" + String.valueOf(data.isSort(ConferenceContext.SORT_UNREAD) ? -ConferenceContext.SORT_UNREAD : ConferenceContext.SORT_UNREAD); %> - New + New <%= rdat.getStdFontTag(null,2) %> <% tmp = self + "&sort=" + String.valueOf(data.isSort(ConferenceContext.SORT_TOTAL) ? -ConferenceContext.SORT_TOTAL : ConferenceContext.SORT_TOTAL); %> - Total + Total <%= rdat.getStdFontTag(null,2) %> <% tmp = self + "&sort=" + String.valueOf(data.isSort(ConferenceContext.SORT_DATE) ? -ConferenceContext.SORT_DATE : ConferenceContext.SORT_DATE); %> - Last Response + Last Response <%= rdat.getStdFontTag(null,2) %>  <% Iterator it = data.getTopicIterator(); %> <% while (it.hasNext()) { %> - <% TopicContext topic = (TopicContext)(it.next()); %> + <% + TopicContext topic = (TopicContext)(it.next()); + tmp = self + "&top=" + String.valueOf(topic.getTopicNumber()) + "&rnm=1"; + %> <%= rdat.getStdFontTag(null,2) %> - <%= topic.getTopicNumber() %> + <%= topic.getTopicNumber() %>   <%= rdat.getStdFontTag(null,2) %> - <%= topic.getName() %> + <%= topic.getName() %> + <% if (topic.isArchived() && !(data.isView(ConferenceContext.DISPLAY_ARCHIVED))) { %> + (archived) + <% } else if (topic.isFrozen()) { %> + (frozen) + <% } // end if %> <%= rdat.getStdFontTag(null,2) %> - <%= topic.getUnreadMessages() %> + <%= topic.getUnreadMessages() %> <%= rdat.getStdFontTag(null,2) %> - <%= topic.getTotalMessages() %> + "><%= topic.getTotalMessages() %> <%= rdat.getStdFontTag(null,2) %> - <%= rdat.formatDateForDisplay(topic.getLastUpdateDate()) %> + <%= rdat.formatDateForDisplay(topic.getLastUpdateDate()) %> <% } // end while (more topics in enumeration) %> diff --git a/web/images/bn_archive_topic.gif b/web/images/bn_archive_topic.gif new file mode 100644 index 0000000000000000000000000000000000000000..fabfe1727eae55b553fc37b5cd91743b38341af3 GIT binary patch literal 979 zcmV;^11$VUNk%v~VNd`V0Q3L=%*)GtA7H0002YnKLsp zGiGKpGcy30W;4vpGiEb0napPY|7J4)0L=ep|NqRHW@Z300GZ4)%>S8YnKLtHGc#rY z000000000000000EC2ui08juJ000I4paBp-0hE(DnnilPFc8G@JJaXzvhZ-kD%>eR zpbQ8kq;k0|L<&c!NjDfw0n`G?Y9&MoT0+DNMGl@`G4$BQ9!IALmNdlx4!YdyH{8$u zFgHS2d?$r`d^3fHeP9O>2VfJCI&^n0I)H#R0yQ-^2LUQSDuQ`fs4n}VV}B|vE8hZH<_z5K{GzAy4A6=2U;jP0t6hJA?<_!5dk_i=#wCut_1=r7--OGNDvhZ z>)Gg$Y#fP8CK@2n1t5XIAr1Z%G)xeH!MB$NA80_JK*54k*ZwHT#nV@kNG+heV4z_k zpoV`8C1-1gz z<-DNfSqla#xRh$MrGa*=I?J^3IHD5Jh9-x4Cnw1Oa`t%gLoMM&Z8QcUV+l{qLs(*g*5A{ zZbJxwoB$RCumMGjN&aTvT$kDA765*wH{J_U{a_zyk%^Wc0tP500zNWamt8yzECf~n zHnAgt0)81M!(}==W`Y4>NsyZ^2*3kC1s3oXkzf*JpkV_&Mpx2FA9m6VD@HXGP;NsA znN^Yr!3Tj12OLxp46t$5kcUPI+1pLM6jUVzwGqJ7CM8W{l`49?^v*{9&5+VTH9*OU zlQke^geN-AP*HqWI#dG{HxL8o54fDb(hv(g*js$nAyrsKxnWS)4`#j~O+Illa)JuB zT%(7MeST6l^Vhpo); zlne#!YRx&kTkbF?NOz#x%O?WxB4|RvQhIDwe^h~iCc!Y(s}J{_vMVB}B$k6OUxrAS zqeseM?7h+q+TlS7b@*Dq0u@|vLMH{3P<#i!y6}^)MJ#c|XC2glz^`dZ@oOOf06Qb_ Bjw=8F literal 0 HcmV?d00001 diff --git a/web/images/bn_delete_topic.gif b/web/images/bn_delete_topic.gif new file mode 100644 index 0000000000000000000000000000000000000000..18b5ab3e07d9f07d719b3f9950169e540928f3ee GIT binary patch literal 969 zcmV;)12+6eNk%v~VNd`V0Q3L=%*)nas?Y0000pGc(MY zGiGKpnPxLHGXQ2YGynf)%*->H%rgKp0GVcHGXMbp|IEz)W|_=pGiEdYnP$xYnPz4H zGiCq)0000000000EC2ui08juJ000I4U;z+-0hE?GnnrrQFc8G@JJaXzvhZ-kD%>eR zs0;`sq;k15JPb#uNjDfw0aOFYY9&MoTEfE%MGl@`G4$BQ9!IAKmNeA>4!YdyH{8$u zFgHS2d?$r`d^3fHeP9O<2VfSFI&^n0I)H#R0yQ-^2LUQSDuQ`fs4E8hZH<_z5K{GzAy4A6=2U>S>FD5V$AYz8J7Yk(c0-zw9j0pk;LdHxA%F;+V6{#xNWc()gO>{sND!g2K!ci7Ybu~g z)|<+P0q7oefS^HFrg`C>P!Iq@AdFu6rQ+1!&)5P90tA4nasdMX3oMhhj2S1qsV@>F zY)~L%fCZER+?-s1@P&m(;T*8Q?au%PWE(QT;9!Ggl}wW|cuZFK0;5Azos% zbizd$iqM~e*0JJLZI_kyoCu{s$iOjK&CpNZohvz&06TcK_Ki|^4n`Ca{QZDmo7QAA6gQ>pp^Y#2 zF{EFlniRyzsx@R1L91qJq=%w5WCToDHoU6PK?Ld9UwD65iC;qn6hr_@g6OAdcICtZ zg9Vz>W}X2e<1_z(>VaC*K$6A`XZ{0CF*Z&EGL6%KqCfy*0WnY>tRG|s z^??q^NRVF`*#0>%H!x^0bO}Gc5aA&=KfpmjtJZJj@q?2&Wn~t5y)krHz{BCPrE0I9 z16TTvKx>IJj9-{8=BXdytYQ(UE68BZuWKquudPUTb{D8{DVAVhkBanPxN~k$hVw@EhUsqc zGZ_!g4WBQxd(oj-*MvET=dM`f&y**x?{^%KV_T;Gfz}dOB{kwnrX)dh49Sbi#p4{1`*n5=JqD9RZ1rt!&&g zj4}=|G&HeIkmQNDaKLFhE7!aLKEtO6*^V&Cr7Wv@sCg+M^h*V2uxmje891FWt` z!>=;)sFd9k;k9ds6Fn?zct_;hzswa4Y>!zdIK=(@k{)Zi&|%8K_+XJ2(M*Ps1ucv! z7uk}N7H(u^sGY1TcA)6{L9>{=9kCJzCg%Jtm$hf~xu8_--%{SfzMk>I!BFWRrBeeL zV?-Zj%GG^b9NKA^HnqUiD8bR0$MYaB!`>~%$Ba}x6CFH*FZ?(%mrcas@!Y)@*Di=Q zx_Msi-}tpCJ%5+xQ+Wmvk&m2g`IWA*b`Rcc3Dyh05E7rSTe!G)c4e7TgJdkju^{!V zo5`0Q-6s@lny#qb6cfeda5N+;Xho?yW0s_$tZPA>@Kp5x4MstU-+wL#v2gD3yu3k& zm+Qa=CY{OM>y+eHpOoF0cgSalTaIdrNb#qdL-Xers0CzA+H2W5J?3)YMla2$X`*^+ zKQ>H#?yamfb#=4{m%{nkVt-kt3cky-n!e=t0poLGvqG$ryp^pCl)YccT{M_f(C3)! w!Pw$b6vwn6rs(9}C4YoYX*~}$@-u2bvixLCSBbi3z0`x^MRxyd(ghf-0qsni=Kufz literal 0 HcmV?d00001 diff --git a/web/images/bn_go.gif b/web/images/bn_go.gif new file mode 100644 index 0000000000000000000000000000000000000000..d8788199375baa1f0c096b2e5270fdd33214a63b GIT binary patch literal 879 zcmV-#1CabjNk%v~VNd`V0Q3L=%*j7RG&D>j1}bp|CVQVU zm8Ss-wVg5r3PUt`yF~^#qXSMU1`%`u5j?0TCV#km2#D6T6_k9yeL|&2JjA0oNCd4V zOFPb!;{YP$)a})$UNr@kM%y<+F}m#|tLy||@S~osQAZxs7yxg8g9Eh!kpogn96U}z zj5Rq2OUEn(n2!7jpl})j2N3=!ERc}MQom6>YSA)94qm?ram+D*rVZXLc-8#E@AoyevZVvYdS=s5F3 zD?y@`Kh=&xu%N+_0}T>UZ~#Dp---0}BC5nyAOgUGvH&2Q zaIip5fS3oN&I{1EoyJJRuJY*&AgiWS031*lB^pAF2BmEft5ic_yh30ijRJvL!dgp2Y_cFpx@6LyB@Y~tSq(TJw1pp&vWjuc0?jD)~H!00~iq#KAoJ1kQc*XNeee#03#H1)qs>$ zn3_2z!U69PFdmNpOq14|>^zgDD0G3xA#`WH;}(*&;X@aZ3;H0*l}z~pgjF9sCL@5; zG31t*wT;OKUVse%k*k0XV5+RIGGwcf2i$7wI}1T7tE<4~3aGHcy4t|7vIaIRpsa-e F06X?fW)T1Y literal 0 HcmV?d00001 diff --git a/web/images/bn_hide.gif b/web/images/bn_hide.gif new file mode 100644 index 0000000000000000000000000000000000000000..21f49f507f2613a47e344d3d9243a22f82463edb GIT binary patch literal 906 zcmV;519kjINk%v~VNd`V0Q3L=%*QPYW@a-00RNe0%>S8YnKLtHGXOIH zW@ct)W&ks005bqHEC2ui08juJ000I4-~bSS0hE+EnnrrQFc8G@JJYA|vaoQ(D%>eR zhzJNIq;k15JPb#uNhc6Y0aOFYY9&MoTEfE%MGBr?G4$BQ9!IAKmNdlx4!YdyH{8$u zFgHS2d?$r`d^3fHeP9O<2VfMDI&^n0I)H#R1vND{2LUQSDuQ`fs4E8hYH<_z5K{GzAy4A6=2U=Jdr;g2#_5y*700l(I7%(7Vp8}x14GB_;Tef2Y zl8qxVED$^Z2p}Gm!N6bx34i_)C@7%7hJ*pS`UJew%af)+Ng_SMljB$?kV6=t1E9de zzyvU}C8&_g(5-FRhTMFI3u2OImt+Q*fj}2QgC-LkR9V0Q(FqP5z;u9CRZM*+x-JM* z!=Hg#tYi=rsY&QU3l2IY*uXMDfDH&IaA?pVK!cXk@WtgCz;8?doj6jk=&s}tc1-~^ zNYH?#g9ZbTiUsg;!hi~+dp6A%)Sx~D5uRN6@phy;UN01geV3rH@DnO0Byb?10Zqv@ zL z0>JbKJAVvtUrqUChs7Tc1OQwB7q|zPCm+#ZjUod10uw@ zLaUe%(lr6x7u5;pyl?>@-VlP-NAQHWm2@yE6ltDY>E)%GA6;idUU?NdLXWiwg2y6U z$$;c{e^mJ0NS=LZMN{ysnpIt#aaL$KRc@M(ZILdtrLQ2}^ix7BHG~;M2z>|9vdbph g35d={>ld^L5;bJ4wFzO{kOB;4n-Hex zm<$Xgq;k0=L2VfPEI&^n0I)H#R0yQ-^2LUQSDuQ`fs4E8hZH<_z5K{GzAy4A6=2U=Jc-fjp$AfrHm00U(pJlKaILIwpEQ0Vky6CVmE zN^#3}Y(TPcB!;m9upj{d5fJ_g2w)Jv0l@|$0ysP&paBF08Xz#hhsB=BZ$LE#N)qW2 z9zC8GGzc&w0fU(W0wh>4!+}r$3iNDZgwz^ScRprBP4euLCwKuem?W5>0D}Y%7$|7a zAVbZ$xv&asfLFjx3sJKM;0pmL00aRZ1ghbWK&{0!7&J(b;AX@G0VE()uzOG7Wme%l z5zZlC)drmvCKzmjx&Fn90)M~;nrE10765~=A<)2Q_&Y1B2zk6xf2sFoEWho zV@x@~lbkT9V2dh=06@V~LmU)Smz$VL&|xQtaDZYXcn7Hr(%3LkMmuoTM-V|k`H6L6 zu`?5X-=u?(4VknPN}Xw<66!|pSVN1PnIeWjYzGOnt3xsUA&o(;%<$uEmx!1MsCq0i z#Zjne;^&m8QGTVRLI7B2W)&A0%UE@qavBUDR^V{X59NsXsICgJVQPYW@cuYGcy1) z|Cwe0Gcy1HGXMYpEC2ui08juJ000I4-~beW0hE|InnrrQFc8G@JJToevM`avD%>eh zpb!cqq;k15L<~o$Nk$H{8$u zFgHS2d?$r`d^3fHeP9U@31AqJI&^n0I)H#R0yQ-^2>~iUDuQ`fs4E8haH<_z5K{GzAy4A5H0|{E47ET?TA+1k?0hTpo^L~f2*~G3NFkT;6O&tceo}N5a3{e1OpWsB2e&PBFq6N6(|rX zA;5(UZTlebxB;OIlwCW@J%QlCO%63~A^?!#00gND4nk!KpaISiWPt)mD_|$k00*Qt zMEYrM!vG8qT!5NWErJ0%1He>(u!F9-ZDGsqGKw5uwgun$8&6OBo z8+cV2bfE$8KwZfxcS2XBEpS?N0VH=)d{Mde+D!p4KtTa}ozUE3CoS;W15v@GCjw`C$%Np0SqdzpeO#BG}HnP9Kd9A2t{Te78Vd?qznQu$AAD6Rc4=GMg}L4 zd>752kS@W=fYfC&FtC^m1NfC(4Z-CICjb<{z<>!QoiJxb0~HcPMMBUf(waH`!9YTz z@+5;1xDXL!p&?~c(J6UQ>PaZ~07BXf444-vAf?vnC;$jWpb{!n(F5#2+329@AbH>+ zDNl}~7AmN`jQ~*4Cseh9E3};2u<0)|UE*vF@T4P6cEAc}>9Jek>TaS0B(v2u0aW`# zNvFsJM=+Z{bB92melzE⪚`@05_yltvxD~iVDKr{@cT*JKPe?DYy)QP<97TH>v~( zMJp{JR0U`9L<%8t!b=5V|oQW!=v&jP-q_fW_^PFhTApih7S2>(4 literal 0 HcmV?d00001 diff --git a/web/images/bn_next_topic.gif b/web/images/bn_next_topic.gif new file mode 100644 index 0000000000000000000000000000000000000000..17ea4c6a2ff6031a7aed47720226d4a4b72660c8 GIT binary patch literal 951 zcmV;o14#TwNk%v~VNd`V0Q3L=%*S8YnKLtHGXMYp z000000000000000EC2ui08juJ000I4paB#B0hEeh zkPHeWq;k0=JP1dqNjDfw0aOCXYAr+y+QP#NMGl@`G4$BQ9!IAKmNdlx4!YdyH{8$u zFgHS2d?$r`d^3fHeP9O<2VfPEI&^n0I)H#R0yQ-^2LUQSDuQ`fs4E8hZH<_z5K{GzAy4A6=2U=Jdr*vhQfxtjIX)<~NKp;av0|N^vbo#N04}}w@ zxMe#wAlWz)!vX;?kiftU1t$JwKtQl^2m%E|3@C_@KtqHE_^{Z6`K_m>KuID!!jt3J zf&>Z}Dp&yFPk>DT5?D*12kIp~*@palhf4sF5znsHfEOTx0tTHJEC4`rf(~V9bD{$*IqrqUc5ezhFuz=)3q%!XIp&I-|X_+&qO06qE07I1w z^&yb(b(%xS)on9zy&zLB^Pl4v~&XkUX64@C(R6TUrZwG_=f=|W%t8cI8xrU=kTfN5%ClZ+OM<|DFgyA%LBoYK+T*2^| zE+)NK&?YGb@Z56-IB-Cmay+%r9fln0Pk^<5QZ|i*{P?i z^nl9_dt?>NbqFCKXeWQZhGTT^{UHsJGCc5;39d3~gEy2;s#7pN$m0$K?s1yXNbM=w zQlM4v5uc|qKZJP&M)fg5i$*oX>1uR_HMv?rC-jQl5TRw)RCYyH=GB8H%$fiHnas?Y%$YL)001*H zGtA60nPxMY%x3@pW-~JYnane0Gc*7H%w}dY%>QPYW@gO)nP!ex zkO&MUq;k0|I0{FoNhc6Y0n`G?YBfX+T7$z2MGBr?G4$BQ9!Dn!mNc~h4!YdyH{8$u zFgHS2d?$r`d^3fHeP9O;2VfMDI&^n0I)H#R12r`_2LUQSDuQ`fs4VjZ*V$6whO+W)5kT7o`jgesHdMpNU%vIq=5y0 zotnH0>E8hYH<_z5K{GzAy4A6=2U=Jbr;g2#_5y*70TXHv7(j5y06{-C$p})4Tef2Y zl8qxVED$^Z2p-A+gn&T;5dr=bFp!u4i>^Ka@AUGdDNvF~kMQI;)(K>;1_2tFd~iZR z!XYzS+VB*rkgXU2YQDquFv+t^Fr$Lh#m;Ad3lSbvSP+2#0|h5qCI~Pf0)Pn|PGWkg z?*xJX3j)>fSD=<&uRy;65P5b0QI&?10$7{i;e#0prs1vh94+XoRQnAV{d!(##o zGAM}9ZmGi2r7!NzEP)YHcsK6K;N{Q;0}L`AY+!J9u-cRhBLle5^G`nUY{d)y)7z4_ zKA?@n3JeptB|~G2DgL0rd!_*(T?86)@4+ zlHcq(z$zw)xR;*R=@P_5gZzL;L?lMV)1nsoX7CxF{Y0I8@<^T+6+ibVUBKsn^&4vH~JDK8rKmY&$ literal 0 HcmV?d00001 diff --git a/web/images/bn_post_go_next.gif b/web/images/bn_post_go_next.gif new file mode 100644 index 0000000000000000000000000000000000000000..612ac7377209da13bb466ea5a44866ebc9adddc7 GIT binary patch literal 997 zcmVH|C#^HnKLsp%*>fHGXMYp z0A^-0%$YNpW;4vpGiEb0|Nmy0%x0O)GXOIH|NqRHW@cuYGcy1H%>QO)GXOJYGtB>) zGXOIH0000000000EC2ui08juJ000I4AORGB0hE|InnrrQFc8G@JJaXzvhaY!D%>eh zpbQEmq;k15KnzEyNjDfw0aOFYYAr+y+5*H2MGl@`G4$BQ9!Dn#mNeY}5W3v!H{8$u zFgHS2d?$r`d^3fHeP9R>2w)hII&^n0I)H#R0yQ-^2mvZTDuQ`fs4E8hZH<_z5K{GzAy4A6=2wDOJ|2k!>ACVBkqW1A&(|0jTf}fB;?G zvK2=j1k~ZfCEH*%(ysiL<0>k6v#NB zAOTgn2?T)si7-I}w4V})s6o^MSP_bNwyX-^uL6Y%L?l=MRv;iV{U+x9tMougzn%d+ zXi$|wgg*)ZP;eka%h_cYXjm}0Q&vwR znIF45Fk$4nbMVlAPt}l zcsIjTGTa%a01#C4L#V#!q)Q?vm{5_XsNnGlmM96pP)bytY7wG@p|gq!rd?CkU_j}? z=^#Adkq;;6ZJ5STmMN{2%c$N-6(RryVMC0dLF}1pt%Vl!>j}P^1prYD;pwYIfE=O8 zn5gJv?hY`F8;q_P{qbf1DItp3!6(GKu0a0)qVIqnWVImRX!Z6I~#DEAm z-KdCi5|q!|7dk; z_vCm3YhS2Yu@;SzY%j5;ax_lPE*MHb7ED2*>YwWFH6idI=?YwM}b&)aZMt|uPUpV`=)mPwLoSgqE0LW&%PnW&gb zA8u3Xllom(>@Y1P>aYb z`ijV}zkdY6n87n7ROI2e1|zv5gT2RxX}|g6Cd&|DY~Zw9QXri&M#-Q3mF z>BF;4i@bjhm|lX}xoAuFgVq>H6i?NVY=mxqa4qG>%Y*w1)NhvKG)WoP;Ms0z8t1^< z84d$WOxobCm4zpU7{@9*R#&vZ1DyQ$tn$HVKEgtQ+@WG*?XP@i^f*rJ1mld!UOz5u|!>9Enei0+9 zFv$bb`^y2ZUZv+kbiFp=%UgaA?ZJ+$enFh7R?E$~Dg$rzf`Khq-P5yaM2|=(PR`!Q zc%Hu&Xn4Vuj#^))zf-#Q)%z1C(5<{>-VqLZSiE7OdmW#e`q-a$YbEkr-&Uz&r*uIKmlRW&)hEqPuXWk(z<*Iyo2w$L#f e8=l=a$Kqtg$W^|7tQV?FLh6;O`x#|^;Ml)UQLg>~ literal 0 HcmV?d00001 diff --git a/web/images/bn_scribble.gif b/web/images/bn_scribble.gif new file mode 100644 index 0000000000000000000000000000000000000000..6978473dbb73a3acc075cb342d41c220dfe24074 GIT binary patch literal 949 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 Yc;U@hX+!NUSH|C#^HnE(I)%*>fHGc#sp zGnr;H%$YMYGXTuYGiEb0GXMbp|7MxYX8-@p%>S9p|7MxYGnr;)|CwfHnKJ-00A^-p zW@Z30W-|aYGXMYpEC2ui08juJ000I4AORGB0hEeh zm<$Req;k15L<~o$NjDfw0aOFYYAr+y+Csz&MGl@`G4$BQ9!IALmNcaR5W3v!H{8$u zFgHS2d?$r`d^3fHeP9R?2w)YFI&^n0I)H#R0yQ-^2mvZTDuQ`fs4E8hZH<_z5K{GzAy4A6=Km8G%toleBA`P900MZB?6ZGSc0s;aS2mn|L)sF=> zdL&CSKtO^#4@3-s2Y`V9T>$<%DRi(P0jCDJN}vQlAi;wJHWkGD=2DZ5B#{U_pkN}w zg>2~t`s}m-rY@2+{s=lyqaq9j?zE*O^2+0jkS!q`z^LHB0i6vR903j!p-=^!K3A!;9_y!jTuS^{c&%AiMLaGnMN6hIcRmTCa130x}BtU&Tfs+ea` zeHfr~kF_Tfpp6JwvS9|Q0VGg(q7$6~8FZ$Wm;kF~0%Zd2MUDZ?{C~#1GNQMWZ4X0G?JhOUx@w3aQ*)%x= z&{A>o3KjxD@W*fp2>$k8O#%ETn+T@0RFe+M0nh+^06-u?N;;K79b~0-mOuamfR>>I z!ga^M1eO5M0R#zXFd|~25g;Q39yGOq3=JgkR!zwb@K*wM0nikY0Nro^ZIbOp00A&I zpulBD_W0I7oeW^d3lPx;*9jMpI1xlA)kYhMGIVm(e#qp-3I;m;K^{}Z(PkWQ--s}Q zOfsB9%?S)llMxLn*pdx5Hw4;;0E%u_<`93N_=yp~VC72y8)mamDRNw*$}LfS!hoVA z_Q#2(Se3LN5vhhEX#j#QmIFON=P1gWZY!kINF^lC~1Rt11kHi%@6t>AdF z$TOZ|(O?aMbXuy=K{F_XP&$?-bWkRi+Cb}|m-xv|B6;*f#SKXkn+}Pv(!}PpG|f<^ zJq7sWr%0Z>Lx?Fsq*4yFvwBjk4C~a^U^>{!fE88%jrn9sa6uFRTqk9G(m@kZeDTK_ aV=A)7Ph#9S8YW@ct)0A@1)EC2ui08juJ000I4-~beW0hEeh zpb!cqq;k15KnzEyNkE8haH<_z5K{GzAy4A6=2U-#V6?C+-F<^io1~Pg9KzQudo&^d7EEpi**NIXm zDrP%IAlWz)0ubCopdiPM1PJ~T0_Y?{#0-WuYQP(^00Bw_DX(oJu%MkL8%d7f5nv%l z1C0x1B)9=HfTn-nXuJgQK*T9qfu!Zqwvq^}5(6H*ASs|hf&>f9CQx8g!Ga8H0SqwP z0DywC3B*1~5aB0DnrCM$h=3Nr-@OSC4gsVE&ySEb5J-$rF#&+37$W>T3g9S201Flj zu?)E^)Dx0Vumznmfk6!e5DZ8QdV#Kt4lIGq8NdTX3jrKhwp>##Nwx|yTyXnfMuU=} z2VAf$=t0Q<7OoGM(I7Tk+X6a$V?e>lj|u_8zIf1KuG6JUV+Xiv0jGeQ7DTTOfS|#H z{xL~5gKRUv_m6BO{r+Wx1Dw3V&3z1IAYD$|trpt}6H!LMY-NcT8h5J@Ae#&eh~=JZ z0d$8zX!3J!ph1{ef@(vK8J5>d-}c z0C5Q(HcZe*oOU*~&`%5%Q~(3dsHBKW_-u1Z5L<1Ni&HOII)g!RR*9*n?Fg{S5De<# zDJw5*@IxY{e_+D{9=b3%gq1SHDMKfO4m(wF>3MoiSGV121FwmyK!qtg1VW88;ub5H zm4$YtuRVMyhDrdcW&;B)FOaYYsidH4l3X(Il1_S@I7O%g3?W45l|u{=kwIuuZ>)yNdJ{AJ4p!Tj=<7f{r3L1=~m06U#Vk%#~Q literal 0 HcmV?d00001