1 # ===================================================================== 2 # cmtPost: post a new comment to the specified TW page. 3 # 4 # Copyright (c) 2007-2011 Carlo Strozzi 5 # 6 # This program is free software; you can redistribute it and/or modify 7 # it under the terms of the GNU General Public License as published by 8 # the Free Software Foundation; version 2 dated June, 1991. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program; if not, write to the Free Software 17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 # 19 # ===================================================================== 20 21 # ===================================================================== 22 # Local variables and functions 23 # ===================================================================== 24 25 cgi.group = () 26 cgi.group.literal = () 27 cgi.page = () 28 cgi.page.literal = () 29 cgi.cmt.stem = () 30 cgi.cmt.parent = () 31 cgi.cmt.subject = () 32 cgi.cmt.excerpt = () 33 cgi.cmt.aumail = () 34 cgi.cmt.auweb = () 35 cgi.cmt.captcha = () 36 cgi.cmt.author = () 37 cgi.paging = 0 # default is no paging. 38 39 tidy_args = -raw 40 thread_node = () 41 42 # ===================================================================== 43 # Main program 44 # ===================================================================== 45 46 # Load call arguments. 47 csaGetArgs POST 48 49 #csaExit.pcdata $TNS_CMS_CONTENT 50 51 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 52 53 . $CSA_ROOT/lib/group-stuff.rc 54 55 . $CSA_ROOT/lib/group-editor.rc 56 57 . $CSA_ROOT/lib/page-stuff.rc 58 59 . $CSA_ROOT/lib/tpl-stuff.rc 60 61 # Prepare the error message "back" button. 62 63 #if (csaIsFullPath --exists --quiet $CSA_TPL_ROOT/tw-nav-back.txt) { 64 # # custom "back" action template. 65 # tpl.include.nav.next = $CSA_TPL_ROOT/tw-nav-back.txt 66 #} else { 67 # # default "back" action template. 68 # tpl.include.nav.next = $tw_dstem/tw-nav-back.txt 69 #} 70 71 # For comments to be enabled it is required that the relevant table 72 # already exists. I need to print an explanatory message, as the comment 73 # link may be present also on pages that do not actually take comments, 74 # or comments may have been disabled while the user was about to post a 75 # message, so she needs to be informed about that. To disable comments 76 # for a page that has already got comments, one possibility is to 77 # manually make the comment file hidden. I prefer not perform automated 78 # table creation here, not even if comments are enabled by default on 79 # all pages for this group. If comments are enabled by default, then 80 # it will suffice that a user calls either cmtList or cmtView prior to 81 # calling this program (a common situation) for the comment table to pop 82 # into existence. 83 84 csaIsFullPath --exists --quiet $tw_pstem+cmt || csaExit.fault 1013 85 86 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 87 88 # Check authorizations. An empty group list means that everyone 89 # can post this comment. Editors are granted access in any case. 90 91 if (!~ $'tbl_page.p_allow' ()) { 92 93 if (!csvMatch $TNS_AUTH_GRP $'tbl_page.p_allow',editor) { 94 95 # Show self-registration form if appropriate. 96 csaIsInteractive && !csaTrue $CSA_AUTH_OK && 97 ~ $TNS_SELFREG_AUTH *[a-z]* && csaTrue $TNS_SELFREG_AUTOASK && 98 csaExit.location $CSA_RPC_URI'?0=showStatic&x-csa-lang='$CSA_LANG^'&1='$'cgi.group'^'&2=tw-reg-form' 99 100 csaExit.needauth 101 } 102 } 103 104 # Check misc. required args. 105 ~ $'cgi.cmt.stem' () && csaExit.fault 0066 # should not occur 106 ~ $'cgi.cmt.author' () && csaExit.fault 0066 # should not occur 107 108 # If neither a subject nor a comment body was provided, this 109 # may occur although the subject is not strictly necessary if 110 # the message body is available. 111 ~ $'cgi.cmt.subject' () && csaExit.fault --back 0038 112 113 # Try and get e-mail from session data if missing. 114 ~ $'cgi.cmt.aumail' () && cgi.cmt.aumail = $CSA_SESSION(13) 115 116 # E-mail required, complain if not available or invalid. 117 ~ $'cgi.cmt.aumail' *'@'*.* || csaExit.fault --back 1015 118 119 # Support for gravatars requires real MD5, so I cannot rely on CSA_CMD_MD, 120 # because that could be anything (SHA, etc.). 121 122 * = `{echo -n $'cgi.cmt.aumail' | md5sum} 123 ~ $1 () && csaExit.fault 0003 md5sum # just in case. 124 tbl_cmt.c_other = $1 125 tpl.var.tw.cmt.aumail.md5 = $1 # set also in templates. 126 127 # As a minimal antispam feature I require that a captcha code be entered 128 # by unauthenticated users. 129 130 if (!csaTrue $CSA_AUTH_OK) { 131 if (~ $'cgi.cmt.captcha' () || !~ $'cgi.cmt.captcha' $CSA_SESSION(10)) { 132 csaExit.fault --back 401 1018 133 } 134 } 135 136 # Check that the entered URL, if any, is acceptable. 137 ~ $'cgi.cmt.auweb' - && csaExit.fault --back 1016 138 139 # Check that the text entered does not exceed the maximum allowed size. 140 ~ $'cgi.cmt.excerpt' - && csaExit.fault --back 1019 $TNS_CMT_MAXLEN 141 142 # Make sure some text was actually entered. 143 test -s $TNS_CMS_CONTENT || csaExit.fault --back 1017 144 145 # Since the introduction of the support of the element it has become 146 # important to always check for well-formedness in the text entered by 147 # the user, in case it contains formatting tags. 148 149 #if (csaTrue $CSA_XMLISH) { 150 if (csaTrue true) { 151 # Make tidy happy by making sure the content is enclosed 152 # inside at least one outer
...
block, i.e. 153 # there must not be outer CDATA sections. 154 155 echo '
' > $tmp1 156 cat $TNS_CMS_CONTENT >> $tmp1 157 echo '
' >> $tmp1 158 159 ~ $CSA_REQ_CHARSET utf-8 && tidy_args = -utf8 160 161 csaSystem --return tidy -config /dev/null \ 162 -indent -quiet -wrap 72 -xml -asxhtml $tidy_args $tmp1 163 164 if (!~ $CSA_STATUS 0) { 165 echo -- ------------------------------------ >> $CSA_SYSERR 166 cat -n $CSA_SYSOUT >> $CSA_SYSERR 167 168 tpl.include.tw.msg = $CSA_SYSERR 169 170 csaExit.fault --back 1008 171 } 172 173 csaMkTemp cmt_text 174 175 # tidy(1) insits on stripping blanks just after a closing XML and 176 # just before line breaks, which is often inappropriate except in 177 # a few cases (add more exceptions as needed). This kludge is 178 # probably far from perfect, so watch out. 179 180 sed 's/\(<\/[^\/>]\+>\)\([^.,:;?!''`)]\)/\1 \2/g 181 s/\([^>]\)$/\1 / 182 s/ *\( \) */\1/g 183 s/ *$/ / 184 s/\([("]\) *$/\1/' $TNS_CMS_CONTENT > $cmt_text 185 186 } else cmt_text = $TNS_CMS_CONTENT 187 188 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 189 190 if (!csaIsConfirmed) { 191 192 tpl.include.tw.page = $cmt_text 193 tpl.var.tw.captcha = $'cgi.cmt.captcha' 194 tpl.var.tw.cmt.parent.id = $'cgi.cmt.parent' 195 196 if (csaIsFullPath --exists --quiet \ 197 $CSA_TPL_ROOT/tw-page-comment-confirm.txt) { 198 # custom editing confirmation template. 199 tpl.include.html.body = $CSA_TPL_ROOT/tw-page-comment-confirm.txt 200 } else { 201 # default editing confirmation template. 202 tpl.include.html.body = $tw_dstem/tw-page-comment-confirm.txt 203 } 204 205 # Make sure no-replace mode is used on the page body by prepending 206 # its name with '-', or any user-supplied CSA tags in the text will 207 # be happily parsed by _envtoxml() !! 208 209 tpl.include.tw.page = -$'tpl.include.tw.page':c 210 211 # PRG confirmation page must be regarded as a view. 212 tpl.if.tw.ispage = '(::DEL:)' 213 tpl.fi.tw.ispage = '(:DEL::)' 214 tpl.if.tw.printable = '(::DEL:)' 215 tpl.fi.tw.printable = '(:DEL::)' 216 tpl.if.tw.isview = () 217 tpl.fi.tw.isview = () 218 219 csaExit.ok $tpl_file 220 } 221 222 # Now set the new comment attributes, loading the relevant ones from 223 # the parent post if specified, and if available. 224 225 tbl_cmt.k_cmt = $'cgi.cmt.stem'-$CSA_PID 226 tbl_cmt.c_subject = $'cgi.cmt.subject' 227 tbl_cmt.c_auweb = $'cgi.cmt.auweb' 228 tbl_cmt.c_ctime = $CSA_TIME_ISO8601 229 tbl_cmt.c_creip = $REMOTE_ADDR 230 tbl_cmt.c_creau = $'cgi.cmt.author' 231 tbl_cmt.c_aumail = $'cgi.cmt.aumail' 232 tbl_cmt.c_excerpt = $'cgi.cmt.excerpt' 233 234 # create new DB node 235 makeNode $CSA_LANG/$'cgi.group'/$'cgi.page' $'tbl_cmt.k_cmt' 236 237 tbl_cmt.k_node = $node_num 238 239 # Set also the values needed by the relevant recent-comment entry. 240 # Now set the new comment attributes, loading the relevant ones from 241 # the parent post if specified, and if available. 242 243 tbl_rcmt.k_rcmt = $'tbl_cmt.k_cmt' 244 tbl_rcmt.rc_page = $'tbl_page.p_name' 245 tbl_rcmt.rc_ctime = $'tbl_cmt.c_ctime' 246 tbl_rcmt.rc_subject = $'tbl_cmt.c_subject' 247 tbl_rcmt.rc_excerpt = $'cgi.cmt.excerpt' 248 249 if (!~ $'cgi.cmt.parent' ()) { 250 251 # Load comment parent data if available. Warning: the comment table 252 # isn't sorted on the primary key, so a linear search is needed. 253 254 filtertable --input $tw_pstem+cmt -- grep -e '^'$'cgi.cmt.parent'$tab | 255 csa-tbl2rc --prefix tbl_pcmt. > $tmp1 256 . $tmp1 257 258 # If the parent is not (or no longer) available I could simply accept 259 # the post anyway but I prefer not to, so that the user is informed 260 # that she is replying to something that does not exist. 261 262 ~ $'tbl_pcmt.k_cmt' $'cgi.cmt.parent' || csaExit.fault 1014 263 264 ~ $'tbl_pcmt.c_tree' R* || csaExit.fault 0066 # should not occur 265 266 tbl_cmt.c_parent = $'tbl_pcmt.k_cmt' 267 tbl_cmt.c_thread = $'tbl_pcmt.c_thread' 268 tbl_cmt.c_tree = $'tbl_pcmt.c_tree'R # append one 'R' 269 270 } else { 271 272 tbl_cmt.c_parent = $'tbl_cmt.k_cmt' 273 tbl_cmt.c_thread = $'tbl_cmt.c_parent' 274 tbl_cmt.c_tree = R # first in tree 275 } 276 277 tbl_rcmt.rc_thread = $'tbl_cmt.c_thread' 278 279 # Turn the comment text into a one-column NoSQL table. I escape 280 # the NoSQL special chars \t and \n here, even if they are not 281 # actually expected to occur; see cmtFilter.awk for more info. 282 283 _awk 'BEGIN{print "\001c_text"} 284 NR>1{printf("\\n")}{gsub(/\t/,"\\t");printf("%s",$0)} 285 END{print ""}' $cmt_text > $tmp1 || csaExit.fault 0003 _awk 286 287 #csaMkTemp debug; justify < $tmp1 > $debug; csaExit.pcdata $debug 288 289 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 290 291 # Set Principal Lock Semaphore(s) (PLS). Unfortunately the lock needs 292 # to be set on the 'recent-comment' table, which is common to all 293 # pages within a group; this may become a bottleneck in case of heavily 294 # commented groups. 295 296 csaLock $tw_gstem/recent-comment+dat || csaExit.fault 297 298 # Create target table if it does not yet exist. 299 if (!csaIsFullPath --exists --quiet $tw_gstem/recent-comment+dat) { 300 maketable --input \ 301 $CSA_ROOT/lib/recent-comment.xrf > $tw_gstem/recent-comment+dat || 302 csaExit.fault 0003 maketable 303 } 304 305 # Test whether the comment table vanished in the meantime, possibly for 306 # having been made hidden to disable comments for this page. 307 308 csaIsFullPath --exists --quiet $tw_pstem+cmt || csaExit.fault 1013 309 310 csaOpen --fast --relaxed $tw_pstem+cmt || csaExit.fault 311 cmt_tmp = $CSA_RESULT 312 313 csaMkTemp tmp2 314 315 envtotable --match '^tbl_cmt__2e[a-z]' \ 316 --strip-names '^tbl_cmt__2e' | paste - $tmp1 > $tmp2 317 318 csaStatus || csaExit.fault 0003 envtotable/paste 319 320 if (!~ $'cgi.cmt.parent' ()) { 321 322 awktable -H '-vparent_='$'tbl_pcmt.k_cmt' \ 323 '-vchild_='$'tbl_cmt.k_cmt' \ 324 '-vtree_='$'tbl_cmt.c_tree' -i $tw_pstem+cmt -- ' 325 BEGIN{len_=length(tree_)} 326 parent_=="" && length($c_tree) < len_ { 327 save_=$0 328 gsub(/[^\t]/,"") # clear all felds. 329 $k_cmt=child_ # set key value for "updtable" 330 $c_seq=++nr_ 331 print 332 parent_="x" # prevent multiple insertions. 333 $0=save_ 334 } 335 $k_cmt==parent_ { # trigger insertion. 336 parent_="" 337 } 338 {$c_seq=++nr_;print} 339 END{ 340 if (parent_!="x") { # insert at table end. 341 gsub(/[^\t]/,"") # clear all felds. 342 $k_cmt=child_ # set key value for "updtable" 343 $c_seq=++nr_ 344 print 345 } 346 }' | updtable --key-columns k_cmt $tmp2 > $cmt_tmp 347 348 csaStatus || csaExit.fault 0003 awktable/updtable 349 350 } else { 351 352 updtable --key-columns k_cmt $tmp2 < $tw_pstem+cmt | 353 awktable -H -- '$c_seq==""{$c_seq=NR}{print}' > $cmt_tmp 354 355 csaStatus || csaExit.fault 0003 updtable/awktable 356 } 357 358 #csaMkTemp debug; viewtable < $cmt_tmp > $debug; csaExit.pcdata $debug 359 360 # Update the recent-comment table and the associated static view. 361 # The recent comments' view needs to be a separate table because it 362 # holds entries from across all pages which have comments, not just 363 # from a single page like the individual +cmt files. 364 365 csaOpen --fast $tw_gstem/recent-comment+dat || csaExit.fault 366 tmp_rcmt = $CSA_RESULT 367 368 # The uniq(1) below is to limit the listing only to latest threads 369 # rather than actual latest comments, not to have a strongly active 370 # thread take up the whole listing. 371 372 envtotable --match '^tbl_rcmt__2e[a-z]' \ 373 --strip-names '^tbl_rcmt__2e' | updtable --stdin \ 374 --key-columns k_rcmt $tw_gstem/recent-comment+dat | 375 sorttable rc_thread rc_ctime:r | uniq -t$tab -W1 | 376 sorttable -r rc_ctime | head -n $TNS_RECENT_COMMENTS_NUM > $tmp_rcmt 377 378 csaStatus || csaExit.fault 0003 envtotable/updtable/sorttable/uniq/head 379 380 csaOpen --fast --relaxed $tw_gstem/recent-comments+xml || csaExit.fault 381 tmp1 = $CSA_RESULT 382 csaAwkCmd groupRecentComments.awk 383 getcolumn --input $tmp_rcmt k_rcmt rc_^(page subject) | $CSA_RESULT > $tmp1 384 csaStatus || csaExit.fault 0003 getcolumn/AWK 385 386 # Set vars needed by the relevant e-mail templates. 387 tpl.var.tw.node = $'tbl_cmt.k_node' 388 tpl.var.tw.url = \ 389 $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri'/comment/$'tbl_cmt.k_cmt' 390 391 # Tell the moderator (if any) about new posts from anonymous users. 392 # Note that the post has not been commited yet, so it may happen 393 # that the moderator is notified of something that will not actually 394 # get posted, but that is acceptable because we keep on the safe 395 # side, and anyway it is unlikely to occur. 396 397 if (!csaTrue $CSA_AUTH_OK && ~ $TNS_EMAIL_MODERATOR *'@'*.*) { 398 399 csaLoadLib csaEmaillib.rc || csaExit.fault 400 401 emaillib_loaded = 1 402 403 if (csaIsFullPath --exists --quiet \ 404 $CSA_TPL_ROOT/tw-newcontent-email.txt) { 405 tpl_file = tw-newcontent-email.txt 406 } else tpl_file = (--file-root $tw_dstem tw-newcontent-email.txt) 407 408 TNS_EMAIL_TO = $TNS_EMAIL_MODERATOR 409 csaSendMail $tpl_file 410 } 411 412 # Queue notifications of new content on this thread for those users 413 # who have subscribet to it. 414 415 if (~ $'tbl_cmt.c_parent' $'tbl_cmt.k_cmt') { 416 417 thread_node = $'tbl_cmt.k_node' 418 419 } else { 420 421 # If no thread node is found (which should not occur unless 422 # there are inconsistencies in the data), next line will 423 # return an empty string, causing the subsequent test to fail 424 # and the notification process to be skipped. 425 426 thread_node = `{awktable -i $tw_pstem+cmt -- \ 427 '$k_cmt == '"$'tbl_cmt.c_parent'"' {print $k_node; exit}'} 428 } 429 430 if (!~ $thread_node ()) { 431 awktable -i $TNS_USER_TABLE -- 'BEGIN{ rc_=1 } 432 { sub(/\|.*/,"",$u_other) } # retain only 1st subfield. 433 # Note: deleted accounts are implicitly excluded by virtue of 434 # having u_other set to "x". 435 "," $u_other "," ~ /,'$thread_node',/ { 436 # Extract notification gateway parameters. 437 # Only e-mail notifications are supported right now. 438 gw_ = $u_other; sub(/,.*/,"",gw_) 439 if (gw_ == "") gw_ = "m" 440 printf("%s^%s ",gw_,$u_email); rc_=0 441 } 442 END { printf ("\n"); exit(rc_) }' > $tmp2 443 444 if (~ $status 0) { 445 446 ~ $emaillib_loaded 1 || csaLoadLib csaEmaillib.rc || csaExit.fault 447 448 if (csaIsFullPath --exists --quiet \ 449 $CSA_TPL_ROOT/tw-thread-updated-email.txt) { 450 tpl_file = tw-thread-updated-email.txt 451 } else tpl_file = (--file-root $tw_dstem tw-thread-updated-email.txt) 452 453 # By overriding the CSA_CMD_SENDMAIL_ variable can trick the 454 # 'csaSendMail' function into not sending mail, but rather acting 455 # as a generic form processor. 456 457 CSA_CMD_SENDMAIL_ = 'cat >> '$tmp2 { csaSendMail $tpl_file } 458 459 # Each time there's new content associated with the same thread 460 # this file will be rewritten, but this is OK because the update 461 # is always related to the same top-level parent. 462 463 csaOnCommit cp $tmp2 $CSA_ROOT/var/tw-thread-updated-$thread_node 464 } 465 } 466 467 # Set pointer to current comment in session. 468 csaSession.set $'tbl_cmt.k_cmt' 14 469 470 # Take the client back to the updated list of page comments. 471 csaExit.ok \ 472 $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri'/comment'?4='$'cgi.paging' 473 474 # End of program.