1 # =====================================================================
2 # updatePage: W-TW page writer.
3 #
4 # Copyright (c) 2007-2014 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.page = ()
27 cgi.subcat = ()
28 cgi.subcat.literal = ()
29 cgi.checksum = ()
30 cgi.author.uri = ()
31 cgi.group.literal = ()
32 cgi.page.literal = ()
33 cgi.page.uri = ()
34 cgi.page.descr = ()
35 cgi.reldate = ()
36 cgi.expdate = $nil # must not be null.
37 cgi.author = ()
38 cgi.tags.tbl = ()
39 cgi.tags.xml = ()
40 cgi.tags.geo = ()
41 cgi.empty = ()
42 cgi.link = ()
43 cgi.store = ()
44 cgi.numeric = ()
45 cgi.comments = ()
46 cgi.filter = parsewiki # secure default. MUST NOT BE NULL!
47
48 # These may not be null, or clearing them won't work.
49 cgi.allow = $nil # default is read access to all
50 cgi.tags = $nil # may not be null
51
52 new_page = ()
53 widget_force = ()
54
55 sum_file = /dev/null
56 tmp2 = /dev/null
57 cmd_recent_links = ()
58 cmd_recent_pages = ()
59 cmd_recent_headlines = ()
60 tidy_args = -raw
61
62 tpl.var.checksum = ()
63
64 # =====================================================================
65 # Main program
66 # =====================================================================
67
68 csaGetArgs POST
69
70 #cp $TNS_CMS_CONTENT /tmp/xxx
71
72 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
73
74 #~ $REMOTE_ADDR 192.168.1.* && {
75 # CSA_RPC2_OK = \
76 # ''$CSA_LANG/$'cgi.group.literal'/$'cgi.page.literal'^''
77 # csaExit.ok
78 #}
79
80 . $CSA_ROOT/lib/group-stuff.rc
81
82 # Check the most important arg.
83 ~ $'cgi.page' () && csaExit.fault 1001
84
85 # Page names beginning with tw-* are reserved for TW use, and usually
86 # refer to views that do not correspond to actual pages on disk.
87
88 ~ $'cgi.page' tw-* && csaExit.fault 1009 $'cgi.page'
89
90 tw_pstem = $tw_gstem/$'cgi.page'
91
92 # Check page required metadata.
93 ~ $'cgi.page.uri' () && csaExit.fault 0041 cgi.page.uri
94 ~ $'cgi.page.literal' () && csaExit.fault 0041 cgi.page.literal
95
96 # Enforce the filter specification possibly received in a session cookie
97 # from the client, based on what is currently allowed for this group.
98 # Letting users input raw, unparsed HTML poses a number of security
99 # concerns, especially related to possible malicious javascript code
100 # injection, whereby the default option MUST be to use an intermediate
101 # language (i.e. "parsewiki"). Only those groups which editors can be
102 # clearly identified should allow raw HTML modes.
103
104 # Programmatic clients, such as Blogger 1.0 desktop blogging programs,
105 # always send plain HTML, which is supposed to be well-formed and that
106 # should therefore always undergo the inspection by tidy(1). Furthermore,
107 # such clients rely on authentication data embedded in the payload of the
108 # request and they may not even return the "twfilter" courtesy session
109 # cookie. But even if they did I would disregard it, because I can only
110 # expect raw HTML code from them. Of course I will then check that the
111 # "rawhtml" mode is allowed for the target group, which means that
112 # Blogger programs can only be used on groups that allow raw HTML
113 # editing. Note that "rawhtml" realy means *no GUI whatsoever", which
114 # is why I require the user to enter valid XHTML code, because she
115 # has full control on input.
116
117 ~ $CSA_PGM(1) CSA2 && cgi.filter = rawhtml
118
119 ~ $'cgi.filter' $TNS_ALLOW_GUI || csaExit.fault 1049
120
121 switch ($'cgi.filter') {
122
123 case parsewiki default; # keep as-is.
124
125 case rawhtml
126 if (csaTrue $CSA_XMLISH) {
127 cgi.filter = tidy
128 ~ $CSA_REQ_CHARSET utf-8 && tidy_args = -utf8
129 } else cgi.filter = sed
130
131 case tinymce nicedit ckeditor
132 cgi.filter = sed
133 }
134
135 # Set group and page provisional meta-data as supplied by the user.
136
137 tpl.var.tw.page = $'cgi.page.literal'
138 tpl.var.tw.page.object = $'tpl.var.tw.page'
139 tpl.var.tw.descr = $'cgi.page.descr'
140 tpl.var.tw.tags = $'cgi.tags'
141 tpl.var.tw.author = $'cgi.author'
142 tpl.var.html.title = $'tpl.var.tw.group'/$'tpl.var.tw.page'
143 tpl.var.tw.page.unx = $'cgi.page'
144
145 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
146
147 . $CSA_ROOT/lib/tpl-stuff.rc
148
149 . $CSA_ROOT/lib/group-editor.rc
150
151 # Set Principal Lock Semaphore(s) (PLS).
152 csaLock $tw_gstem/page+dat || csaExit.fault
153
154 # Create target table if it does not yet exist.
155 if (!csaIsFullPath --exists --quiet $tw_gstem/page+dat) {
156 maketable --input \
157 $CSA_ROOT/lib/page.xrf > $tw_gstem/page+dat ||
158 csaExit.fault 0003 maketable
159 }
160
161 # Load page meta-data, if available.
162
163 keysearch $'cgi.page' $tw_gstem/page+dat |
164 csa-tbl2rc --prefix tbl_page. > $tmp1; . $tmp1
165
166 # Set additional attributes if new page.
167
168 if (~ $'tbl_page.k_page' ()) {
169
170 new_page = true
171
172 tbl_page.k_page = $'cgi.page'
173 tbl_page.p_name = $'cgi.page.literal'
174 tbl_page.p_uri = $'cgi.page.uri'
175 tbl_page.p_creau = $'cgi.author'
176 tbl_page.p_ctime = $CSA_TIME_ISO8601
177 tbl_page.p_vtime = 0,$CSA_TIME_ISO # default ranking and 'vtime'.
178 tbl_page.p_npriv = 0
179 tbl_page.p_npub = 0
180 tbl_page.p_ntbk = 0
181 tbl_page.p_ncmt = -1 # comments are disabled by default.
182 tbl_page.p_link = $'cgi.link'
183
184 makeNode $CSA_LANG/$'cgi.group'/$'cgi.page'
185
186 tbl_page.k_node = $node_num
187
188 } else {
189
190 sum_file = $tw_pstem+wki
191
192 # Fix old pages with ctime not already in strict ISO8601 format.
193 ~ $'tbl_page.p_ctime' *[-+]??:?? || tbl_page.p_ctime = \
194 ``$nl{date -d $'tbl_page.p_ctime' '+%Y-%m-%dT%H:%M:%S%:z'}
195 }
196
197 # Whether the page is new or old, set new editor, description,
198 # catalog data and virtual date, if supplied by the user.
199
200 ~ $'cgi.author' () || tbl_page.p_modau = $'cgi.author'
201 ~ $'cgi.reldate' () || tbl_page.p_vtime = $'cgi.reldate'
202 #~ $'cgi.page.descr' () || tbl_page.p_descr = $'cgi.page.descr'
203 ~ $'cgi.store' :* && cgi.store = $'tbl_page.k_node'$'cgi.store'
204 tbl_page.p_descr = $'cgi.page.descr'
205 tbl_page.p_store = $'cgi.store'
206 tbl_page.p_etime = $'cgi.expdate'
207
208 # Make the page body hidden if empty, and if not already hidden.
209 if (!~ $'cgi.empty' () && !~ $'tbl_page.p_descr' -*) {
210 tbl_page.p_descr = -$'tbl_page.p_descr'
211 }
212
213 switch ($CSA_PGM($#CSA_PGM)) {
214
215 case mt.publishPost
216 # Make page visible if called for this very purpose.
217 tbl_page.p_descr = ``(){echo -n $'tbl_page.p_descr' | sed 's,^-,,'}
218
219 case blogger.deletePost
220 # Logically deleted pages MUST have their description set to
221 # a single hyphen, with no other text forrowing it, or a number
222 # of other TW views will break.
223 tbl_page.p_descr = -
224 }
225
226 tbl_page.p_modip = $REMOTE_ADDR
227 tbl_page.p_mtime = $CSA_TIME_ISO8601
228
229 if (!~ $CSA_PGM($#CSA_PGM) mt.publishPost) {
230 tbl_page.p_allow = $'cgi.allow'
231 tbl_page.p_tags = $'cgi.tags'
232 tbl_page.p_link = $'cgi.link'
233 }
234
235 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
236
237 # Set template vars to their final values.
238 tpl.var.tw.page = $'tbl_page.p_name'
239 tpl.var.tw.page.object = $'tpl.var.tw.page'
240 tpl.var.tw.descr = $'tbl_page.p_descr'
241 tpl.var.tw.author = $'tbl_page.p_modau'
242 tpl.var.tw.page.store = $'tbl_page.p_store'
243 tpl.var.tw.page.ping.count = $'tbl_page.p_ntbk'
244 tpl.var.tw.page.att.count.priv = $'tbl_page.p_npriv'
245 * = ``(' ':T+-){echo -n $'tbl_page.p_mtime'}
246 tpl.var.tw.chgdate = $1-$2-$3
247 tpl.var.tw.chgtime = $4:$5
248 tpl.var.html.title = $'tpl.var.tw.group'/$'tpl.var.tw.page'
249 tpl.var.tw.page.unx = $'cgi.page'
250
251 tpl.var.tw.node = $'tbl_page.k_node'
252
253 # Page description ultimately defaults to group/page name in templates.
254 ~ $'tpl.var.tw.descr' () && tpl.var.tw.descr = $'tpl.var.html.title'
255
256 if (!~ $CSA_PGM($#CSA_PGM) mt.publishPost) {
257 ~ $'tbl_page.p_descr' () && tbl_page.p_descr = $'tpl.var.tw.descr'
258 }
259
260 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
261
262 # Policy checks should always come after the inclusion of group
263 # (and possibly also page) meta-data.
264
265 ~ ,$TNS_AUTH_GRP, *,editor,* || csaExit.needauth
266
267 csaSum --file $sum_file
268
269 # This is already contained in CSA_RWA but setting it again here
270 # wont' hurt and it makes things more clear.
271 tpl.var.checksum = $CSA_RESULT
272
273 # Make sure the requested record hasn't changed in the meantime.
274 # (old logic, leave as comment for a while).
275 #if (csaIsInteractive && !~ $CSA_RESULT $'cgi.checksum') {
276 # CSA_EXIT_SCRIPT = ($CSA_EXIT_SCRIPT back)
277 # #~ $'cgi.checksum' () && csaExit.fault 1026
278 # csaExit.fault 0026
279 #}
280
281 # Checksum-based race tests are done only if the client provided a
282 # previous checksum value. If she didn't, then this test is skipped,
283 # assuming that the client does not want this test to be done. This is
284 # necessary also to make the "blogger.editPost" Blogger API method work,
285 # as the API does not provide any checksum argument.
286
287 if (!~ $'cgi.checksum' ()) {
288
289 csaSum --file $sum_file
290
291 # This is already contained in CSA_RWA but setting it again here
292 # wont' hurt and it makes things more clear.
293 tpl.var.checksum = $CSA_RESULT
294
295 # Make sure the requested record hasn't changed in the meantime.
296 if (!~ $'tpl.var.checksum' $'cgi.checksum') {
297 csaIsInteractive && CSA_EXIT_SCRIPT = ($CSA_EXIT_SCRIPT back)
298 csaExit.fault 0026
299 }
300 }
301
302 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
303
304 # Make sure static views are always built using interactive-type
305 # CSA URLs if we are in RPC mode.
306 ~ $CSA_PGM(1) CSA2 && CSA_RPC_URI = $CSA_RPC_URI/I
307
308 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
309
310 if (!csaIsConfirmed) {
311
312 if (~ $'cgi.filter' tidy) {
313
314 # Temporarily comment-out CPIs, or tidy(1) will complain if
315 # they fall outside proper #CDATA sections.
316
317 sed 's/\((:[^)]\+:)\)//g' $TNS_CMS_CONTENT > $tmp1 ||
318 csaExit.fault 0003 sed
319
320 csaSystem --return tidy -config /dev/null \
321 -indent -quiet -wrap 72 -xml -asxhtml $tidy_args $tmp1
322
323 if (!~ $CSA_STATUS 0) {
324 echo -- ------------------------------------ >> $CSA_SYSERR
325 cat -n $CSA_SYSOUT >> $CSA_SYSERR
326
327 # if (csaIsFullPath --exists --quiet \
328 # $CSA_TPL_ROOT/tw-nav-back.txt) {
329 # # custom "back" action template.
330 # tpl.include.nav.next = $CSA_TPL_ROOT/tw-nav-back.txt
331 # } else {
332 # # default "back" action template.
333 # tpl.include.nav.next = $tw_dstem/tw-nav-back.txt
334 # }
335
336 tpl.include.tw.msg = $CSA_SYSERR
337
338 csaExit.fault --back 1008
339 }
340
341 # tidy(1) insists on stripping blanks just after a closing XML and
342 # just before line breaks, which is often inappropriate except in
343 # a few cases (add more exceptions as needed). This kludge is
344 # probably far from perfect, so watch out. Note that the very
345 # first line restores CPIs that were previously commented out.
346
347 sed 's//\1/g
348 s/\(<\/[^\/>]\+>\)\([^.,:;?!''`)]\)/\1 \2/g
349 s/\([^>]\)$/\1 /
350 s/ *\( \) */\1/g
351 s/ *$/ /
352 s/\([("]\) *$/\1/' $TNS_CMS_CONTENT > $tmp1
353
354 } else {
355
356 if (~ $'cgi.filter' parsewiki) {
357 $TNS_CMD_WIKI2HTML $TNS_CMS_CONTENT > $tmp1
358 } else sed -f $CSA_ROOT/lib/xmlbreak.sed $TNS_CMS_CONTENT > $tmp1
359 }
360
361 tpl.include.tw.page = $tmp1
362
363 if (csaIsFullPath --exists --quiet \
364 $CSA_TPL_ROOT/tw-edit-page-confirm.txt) {
365 # custom editing confirmation template.
366 tpl.include.html.body = $CSA_TPL_ROOT/tw-edit-page-confirm.txt
367 } else {
368 # default editing confirmation template.
369 tpl.include.html.body = $tw_dstem/tw-edit-page-confirm.txt
370 }
371
372 # Make sure no-replace mode is used on the page body by prepending
373 # its name with '-', or any user-supplied CSA tags in the text will
374 # be happily parsed by _envtoxml() !!
375
376 tpl.include.tw.page = -$'tpl.include.tw.page':c
377
378 # PRG confirmation page must be regarded as a view.
379 tpl.if.tw.ispage = '(::DEL:)'
380 tpl.fi.tw.ispage = '(:DEL::)'
381 tpl.if.tw.printable = '(::DEL:)'
382 tpl.fi.tw.printable = '(:DEL::)'
383 tpl.if.tw.isview = ()
384 tpl.fi.tw.isview = ()
385
386 csaExit.ok $tpl_file
387 }
388
389 # Create the target stuff as necessary.
390
391 if (!csaIsFullPath --exists --quiet $tw_pstem+wki) {
392
393 # Let's be very careful about the public upload directory, as it is
394 # located in an area wich is accessible to the webmaster through FTP.
395 # In theory, the user could remove the directory and re-create it with
396 # different permissions (at least while the directory is still empty).
397 # If he did, then our next touch(1) will fail and we will abort the
398 # operation with complaints. A user can remove a non-owned directory from
399 # an owned directory tree only if the directory is empty, so we touch a
400 # placeholder into it first, just in case. All this should give us enough
401 # security.
402
403 test -d $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group' ||
404 csaExit.fault 0008 $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group'
405
406 touch $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group'/.touch ||
407 csaExit.fault 0009 \
408 $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group'/.touch
409 new_page = true
410 }
411
412 # Bail-out with an error if this is supposed to be a new post
413 # over RPC2 but the relevant page already exists.
414
415 ~ $CSA_PGM(1) CSA2 && ~ $CSA_PGM($#CSA_PGM) *.newPost &&
416 !csaTrue $new_page && csaExit.fault 1022 $'cgi.page.literal'
417
418 # Now create any other loosely associated files if necessary.
419 # Tidy(1) gives us much more than a cp(1) here, and is still
420 # only one process.
421
422 csaOpen --fast --relaxed $tw_pstem+wki || csaExit.fault
423
424 tmp1 = $CSA_RESULT
425
426 if (~ $'cgi.filter' tidy) {
427
428 # Temporarily comment-out CPIs, or tidy(1) will complain
429 # if they fall outside proper #CDATA sections.
430
431 sed 's/\((:[^)]\+:)\)//g' $TNS_CMS_CONTENT > $tmp1 ||
432 csaExit.fault 0003 sed
433
434 csaSystem --return tidy -config /dev/null \
435 -indent -quiet -wrap 72 -xml -asxhtml $tidy_args $tmp1
436
437 if (!~ $CSA_STATUS 0) {
438 echo -- ------------------------------------ >> $CSA_SYSERR
439 cat -n $CSA_SYSOUT >> $CSA_SYSERR
440 tpl.include.tw.msg = $CSA_SYSERR
441 csaExit.fault --back 1008
442 }
443
444 # tidy(1) insists on stripping blanks just after a closing XML and
445 # just before line breaks, which is often inappropriate except in
446 # a few cases (add more exceptions as needed). This kludge is
447 # probably far from perfect, so watch out. Note that the very
448 # first line restores CPIs that were previously commented out.
449
450 sed 's//\1/g
451 s/\(<\/[^\/>]\+>\)\([^.,:;?!''`)]\)/\1 \2/g
452 s/\([^>]\)$/\1 /
453 s/ *\( \) */\1/g
454 s/ *$/ /
455 s/\([("]\) *$/\1/' $CSA_SYSOUT > $tmp1
456
457 } else {
458
459 if (~ $'cgi.filter' parsewiki) {
460 csaOpen --fast --relaxed $tw_pstem+pwk || csaExit.fault
461 cp $TNS_CMS_CONTENT $CSA_RESULT || csaExit.fault 0003 cp
462 $TNS_CMD_WIKI2HTML $TNS_CMS_CONTENT > $tmp1
463 } else sed -f $CSA_ROOT/lib/xmlbreak.sed $TNS_CMS_CONTENT > $tmp1
464 }
465
466 # Disregard empty page body if called just to publish.
467 ~ $CSA_PGM($#CSA_PGM) mt.publishPost && cp $tw_pstem+wki $tmp1
468
469 TNS_NEW_CONTENT = $tmp1
470
471 #if (~ $REMOTE_ADDR 192.168.1.2) {
472 # cp $TNS_NEW_CONTENT /tmp/xxx
473 # csaExit.env
474 #}
475
476 # Update the page meta-data table.
477
478 csaOpen --fast $tw_gstem/page+dat || csaExit.fault
479 tmp_pages = $CSA_RESULT # needed several times below.
480
481 csaMkTemp tmp2
482
483 envtotable --match '^tbl_page__2e[a-z]' \
484 --strip-names '^tbl_page__2e' --output $tmp2 ||
485 csaExit.fault 0003 envtotable
486
487 tabletolist --no-header --no-footer --input $tmp2 |
488 sed -e 1i$eoh^Column$tab$eoh^Table$tab$eoh^Value$tab$eoh^Action \
489 -e s/$tab/$tab^page$tab/ -e 's/$/'$tab^Insert/ |
490 constraint $CSA_ROOT/lib/schema.dat >/dev/null
491
492 csaStatus || csaExit.fault 0080 # database exception detected
493
494 updtable --key-columns k_page $tmp2 < $tw_gstem/page+dat |
495 sorttable > $tmp_pages
496
497 csaStatus || csaExit.fault 0003 updtable/sorttable
498
499 if (!~ $'cgi.tags' $nil) {
500 # Create the tag table if it does not yet exist, then insert
501 # the new tags into it.
502 if (!csaIsFullPath --exists --quiet $tw_gstem/tag+dat) {
503 maketable --input \
504 $CSA_ROOT/lib/tag.xrf > $tw_gstem/tag+dat ||
505 csaExit.fault 0003 maketable
506 }
507
508 csaOpen --fast --relaxed $tw_gstem/tag+dat || csaExit.fault
509 tmp1 = $CSA_RESULT
510
511 {
512 # Remove all tags previously associated with the current
513 # page as they are to be replaced with the new ones entered.
514
515 awktable -H -i $tw_gstem/tag+dat -vp_'='$'cgi.page' -- \
516 '$k_page == p_ {next}{print}'
517
518 # Exclude page from updated tag table if hidden/deleted.
519 if (!~ $'cgi.tags' () && !~ $'tbl_page.p_descr' -*) {
520
521 # Handle as many '%s' printf(1) args as needed. This kludge
522 # is necessary because updatePage.awk does not always have
523 # a way to know the actual page name in addition to its
524 # unixified version. This can happen whenever an editor
525 # (especially if using an API client) references the target
526 # page by its unixified name. So the only safe place where
527 # the actual page name can be placed inside the cgi.tags.tbl
528 # fragment is here, not in updatePage.awk.
529
530 tns1 = ()
531 * = `{echo $'cgi.tags.tbl'}
532 while (!~ $1 ()) {
533 ~ $1 %s && tns1 = ($tns1 $'tbl_page.p_name')
534 shift
535 }
536 printf $'cgi.tags.tbl' $tns1
537 }
538
539 } | sorttable -u > $tmp1
540
541 csaStatus || csaExit.fault 0003 sorttable:tag-cloud
542
543 # Update the tag-cloud static view.
544
545 csaTrue $CSA_AUDIT &&
546 csaTrapFile $tw_gstem/RCS/^((tag+dat tag-cloud+xml)^,v)
547
548 csaOpen --fast --relaxed $tw_gstem/tag-cloud+xml || csaExit.fault
549 tmp1 = $CSA_RESULT
550 csaAwkCmd groupTagCloud.awk
551 getcolumn --input $tmp_pages p_^(name tags descr) |
552 $CSA_RESULT > $tmp1
553
554 csaStatus || csaExit.fault 0003 getcolumn/AWK:tag-cloud
555
556 # Update the page keywords static object.
557
558 csaTrapFile $tw_gstem/RCS/$'cgi.page'-meta+xml,v
559
560 csaOpen --fast --relaxed $tw_pstem-meta+xml || csaExit.fault
561 tmp1 = $CSA_RESULT
562
563 echo '' > $tmp1
565
566 # The geo.placename attribute is currently hard-wired to page name.
567 echo '' >> $tmp1
569
570 if (~ $'cgi.tags.xml' *[a-z]*) {
571
572 #echo '' >> $tmp1
574
575 # keywords should rather appear in this other way.
576 {
577 echo -n ''
580 } >> $tmp1
581 }
582
583 # Note that no special checks are done on the well-formedness of Geo
584 # values. If any more checks are desired they will have to be done
585 # in updatePage.awk .
586
587 if (!~ $'cgi.tags.geo'(2) ()) {
588
589 echo '' >> $tmp1
591 echo '' >> $tmp1
593
594 ~ $'cgi.tags.geo'(3) () ||
595 echo '' >> $tmp1
597 }
598
599 ~ $'tpl.var.tw.author' () ||
600 echo '' >> $tmp1
602
603 csaStatus || csaExit.fault 0009 $tmp1
604 }
605
606 if (~ $'cgi.subcat' [a-z]*) {
607
608 csaTrapFile $tw_gstem/RCS/cat-cloud+xml,v
609
610 # Update the subcat-cloud static view.
611 csaOpen --fast --relaxed $tw_gstem/cat-cloud+xml || csaExit.fault
612 tmp1 = $CSA_RESULT
613 csaAwkCmd groupCatCloud.awk
614 getcolumn --input $tmp_pages p_^(name descr) |
615 $CSA_RESULT > $tmp1
616
617 csaStatus || csaExit.fault 0003 getcolumn/AWK:cat-cloud
618 }
619
620 # Update the recent-links static view. Note that this view is
621 # unrestricted so we need to be conservative and exclude any
622 # hidden/redirected pages from it. I do not exclude restricted pages
623 # though, as the idea is that page titles and their short descriptions
624 # can always be publicly accessible, and that only the page body should
625 # abide by those restrictions. After all, being page comments always
626 # publicly readable there is always a partial exposure of stuff that may
627 # relate to restricted content, so trying to be absolutely water-tight
628 # in only a few places does not make much sense.
629
630 csaOpen --fast --relaxed $tw_gstem/recent-links+xml || csaExit.fault
631 tmp1 = $CSA_RESULT
632 csaAwkCmd groupRecentLinks.awk
633 cmd_recent_links = $CSA_RESULT
634 awktable -i $tmp_pages -- 'BEGIN{print "\001p_name\t\001p_ctime" \
635 "\t\001p_modip\t\001p_creau\t\001p_descr\t\001p_uri\t\001p_vtime"
636 }
637 # Handle page expiration dates, accounting for older
638 # versions of page+dat which may lack that field.
639 p_etime/=1 {
640 # Set default expiration date.
641 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59"
642 if ("'$CSA_TIME_ISO'" >= $p_etime && \
643 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr
644 }
645 # Handle explicit exclusions from static views first.
646 $p_descr ~ /^ *!/ {next}
647 # Exclude hidden/redirected pages from output. Redirections which
648 # are NOT to be excluded must be *immediately* preceeded by "+" .
649 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)}
650 $p_descr !~ /(^ *-|\(:redirect )/ {
651 sub(/\(:\+redirect /,"(:redirect ",$p_descr)
652 print $p_name,$p_ctime,$p_modip,$p_creau,$p_descr,$p_uri,$p_vtime
653 }' | sorttable -r $TNS_RECENT_LINKS_PROP(1) |
654 head -n $TNS_RECENT_LINKS_PROP(2) | $cmd_recent_links > $tmp1
655
656 # It looks like that GNU sort(1) traps SIGPIPE, and returns 2 on both
657 # SIGPIPE and EPIPE. This does not stand for an error, because it
658 # is due to head(1) closing the pipeline after the specified number
659 # of lines have been read. Furthermore, it does not always happen,
660 # possibly depending on the size of the input data, on the machine
661 # load and on other momentary factors. Ignoring error code 2 seems
662 # to fix that.
663
664 csaStatus sigpipe 2 || csaExit.fault 0003 awktable/sorttable/head/AWK
665
666 # Update the recent-pages static view. Note that this view is
667 # unrestricted so we need to be conservative and exclude any
668 # hidden/redirected/restricted pages from it. Also note how,
669 # unlike the recent-links view, I use 'vtime' here (as well
670 # as for the 'recent-headlines' view), as I think it makes more
671 # sense here.
672
673 csaOpen --fast --relaxed $tw_gstem/recent-pages+xml || csaExit.fault
674 tmp1 = $CSA_RESULT
675 csaAwkCmd groupRecentPages.awk
676 cmd_recent_pages = $CSA_RESULT
677 TNS_FILTER_ARGS = groupRecentPages
678 awktable -i $tmp_pages -- 'BEGIN {
679 print "\001k_page\t\001p_vtime\t\001p_name" \
680 "\t\001p_modau\t\001p_uri\t\001p_descr\t\001p_etime"
681 }
682 $p_vtime !~ /^[0-9],/ {$p_vtime = "0," $p_vtime} # default ranking
683
684 # Safety-measure for backward compatibility.
685 !p_etime {p_etime = 99}
686
687 # Handle page expiration date.
688 {
689 # Set default expiration date.
690 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59"
691 if ("'$CSA_TIME_ISO'" >= $p_etime && \
692 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr
693 }
694 # Handle explicit exclusions from static views first.
695 $p_descr ~ /^ *!/ {next}
696 # Exclude restricted/hidden/redirected pages from output. Redirections
697 # which are NOT to be excluded must be *immediately* preceeded by "+" .
698 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)}
699 $p_allow=="" && $p_descr !~ /(^ *-|\(:redirect )/ {
700 sub(/\(:\+redirect /,"(:redirect ",$p_descr)
701 print $k_page,$p_vtime,$p_name,$p_modau,$p_uri,$p_descr,$p_etime
702 }' | sorttable -r p_vtime | head -n $TNS_RECENT_PAGES_NUM |
703 $cmd_recent_pages > $tmp1
704
705 csaStatus sigpipe 2 || csaExit.fault 0003 getcolumn/sorttable/head/AWK
706
707 # Update the recent-headlines static view.
708 csaOpen --fast --relaxed $tw_gstem/recent-headlines+xml || csaExit.fault
709 tmp1 = $CSA_RESULT
710 csaAwkCmd groupRecentHeadlines.awk
711 cmd_recent_headlines = $CSA_RESULT
712 TNS_FILTER_ARGS = groupRecentHeadlines
713 awktable -i $tmp_pages -- 'BEGIN{print "\001k_page\t\001p_vtime" \
714 "\t\001p_name\t\001p_modau\t\001p_uri\t\001p_descr\t\001p_etime"}
715
716 # Safety-measure for backward compatibility.
717 !p_etime {p_etime = 99}
718
719 # Handle page expiration dates.
720 {
721 # Set default expiration date.
722 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59"
723 if ("'$CSA_TIME_ISO'" >= $p_etime && \
724 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr
725 }
726 # Handle explicit exclusions from static views first.
727 $p_descr ~ /^ *!/ {next}
728 # Exclude hidden/redirected pages. Redirections which are
729 # NOT to be excluded must be *immediately* preceeded by "+" .
730 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)}
731 $p_descr ~ /(^ *-|\(:redirect )/ {next}
732 {sub(/\(:\+redirect /,"(:redirect ",$p_descr)}
733 $p_vtime !~ /^[0-9],/ {$p_vtime = "0," $p_vtime} # default ranking
734 {print $k_page,$p_vtime,$p_name,$p_modau,$p_uri,$p_descr,$p_etime}' |
735 sorttable -r p_vtime | head -n $TNS_RECENT_HEADLINES_NUM |
736 $cmd_recent_headlines | $TNS_VIEW_FILTER > $tmp1
737
738 csaStatus sigpipe 2 || csaExit.fault 0003 getcolumn/sorttable/head/AWK
739
740 # Update the recent-changes static view.
741 csaOpen --fast --relaxed $tw_gstem/recent-changes+xml || csaExit.fault
742 tmp1 = $CSA_RESULT
743 csaAwkCmd groupRecentChanges.awk
744 awktable -i $tmp_pages -- 'BEGIN{print \
745 "\001p_uri\t\001p_name\t\001p_mtime\t\001p_modip\t\001p_descr"}
746 # Handle page expiration dates, accounting for older
747 # versions of page+dat which may lack that field.
748 p_etime/=1 {
749 # Set default expiration date.
750 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59"
751 if ("'$CSA_TIME_ISO'" >= $p_etime && \
752 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr
753 }
754 # Only hidden pages are excluded from this particular view.
755 # Strip any redirection URL from page visible description.
756 {sub(/ *\+\(:redirect .*/,_NULL,$p_descr)}
757 # Remove any explicit exclusion directive.
758 {sub(/^ *! */,"",$p_descr)}
759 {print $p_uri,$p_name,$p_mtime,$p_modip,$p_descr}' |
760 sorttable -r p_mtime | head -n $TNS_RECENT_CHANGES_NUM |
761 $CSA_RESULT > $tmp1
762
763 csaStatus sigpipe 2 || csaExit.fault 0003 getcolumn/sorttable/head/AWK
764
765 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
766
767 # In case the versioning subdir does not exist yet (no TPC needed here).
768 mkdir -p $tw_gstem/RCS || csaExit.fault 0003 mkdir
769
770 # Update the -recent-pages static view, if applicable.
771 if (~ $'cgi.subcat' [a-z]*) {
772 ~ $tmp2 /dev/null && csaMkTemp tmp2 # may still be undefined.
773 keysearch --partial $'cgi.subcat'. $tmp_pages |
774 awktable -- 'BEGIN {
775 print "\001k_page\t\001p_vtime\t\001p_name" \
776 "\t\001p_modau\t\001p_uri\t\001p_descr\t\001p_etime"
777 }
778
779 # Safety-measure for backward compatibility.
780 !p_etime {p_etime = 99}
781
782 # Handle page expiration dates, accounting for older
783 # versions of page+dat which may lack that field.
784 {
785 # Set default expiration date.
786 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59"
787 if ("'$CSA_TIME_ISO'" >= $p_etime && \
788 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr
789 }
790 # Handle explicit exclusions from static views first.
791 $p_descr ~ /^ *!/ {next}
792 # Exclude restricted/hidden/redirected pages from output. Redirections
793 # which are NOT to be excluded must be *immediately* preceeded by "+" .
794 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)}
795 $p_allow!="" || $p_descr ~ /(^ *-|\(:redirect )/ {next}
796 {sub(/\(:\+redirect /,"(:redirect ",$p_descr)}
797 $p_vtime !~ /^[0-9],/ {$p_vtime = "0," $p_vtime} # default ranking
798 {print $k_page,$p_vtime,$p_name,$p_modau,$p_uri,$p_descr,$p_etime}' |
799 sorttable -r p_vtime > $tmp2
800 csaStatus || csaExit.fault 0003 keysearch/awktable/sorttable
801
802 csaOpen --fast --relaxed \
803 $tw_gstem/$'cgi.subcat'-recent-pages+xml || csaExit.fault
804 tmp1 = $CSA_RESULT
805
806 head -n $TNS_RECENT_PAGES_NUM $tmp2 | $cmd_recent_pages > $tmp1
807 csaStatus || csaExit.fault 0003 head/AWK
808 csaTrapFile $tw_gstem/RCS/$'cgi.subcat'-recent-pages+xml,v
809
810 # Update the -recent-headlines static view.
811 csaOpen --fast --relaxed \
812 $tw_gstem/$'cgi.subcat'-recent-headlines+xml || csaExit.fault
813 tmp1 = $CSA_RESULT
814
815 head -n $TNS_RECENT_HEADLINES_NUM $tmp2 | $cmd_recent_headlines > $tmp1
816 csaStatus || csaExit.fault 0003 head/AWK
817 csaTrapFile $tw_gstem/RCS/$'cgi.subcat'-recent-headlines+xml,v
818
819 # Update the -recent-links static view.
820 csaOpen --fast --relaxed \
821 $tw_gstem/$'cgi.subcat'-recent-links+xml || csaExit.fault
822 tmp1 = $CSA_RESULT
823
824 keysearch --partial $'cgi.subcat'. $tmp_pages |
825 awktable -- 'BEGIN{print "\001p_name\t\001p_ctime" \
826 "\t\001p_modip\t\001p_creau\t\001p_descr\t\001p_uri\t\001p_vtime"
827 }
828 # Handle page expiration dates, accounting for older
829 # versions of page+dat which may lack that field.
830 p_etime/=1 {
831 # Set default expiration date.
832 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59"
833 if ("'$CSA_TIME_ISO'" >= $p_etime && \
834 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr
835 }
836 # Handle explicit exclusions from static views first.
837 $p_descr ~ /^ *!/ {next}
838 # Exclude hidden/redirected pages. Redirections which are
839 # NOT to be excluded must be *immediately* preceeded by "+" .
840 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)}
841 $p_descr !~ /(^ *-|\(:redirect )/ {
842 {sub(/\(:\+redirect /,"(:redirect ",$p_descr)}
843 print $p_name,$p_ctime,$p_modip,$p_creau,$p_descr,$p_uri,$p_vtime
844 }' | sorttable -r $TNS_RECENT_LINKS_PROP(1) |
845 head -n $TNS_RECENT_LINKS_PROP(2) | $cmd_recent_links > $tmp1
846 csaStatus sigpipe 2 || csaExit.fault 0003 awktable/sorttable/head/AWK
847 csaTrapFile $tw_gstem/RCS/$'cgi.subcat'-recent-links+xml,v
848 }
849
850 # Create page widgets if necessary.
851
852 # Set target year/month for the calendar widget.
853 * = ``(-,){echo -n $'tbl_page.p_vtime'}
854 ~ $#* 4 && shift # strip ranking
855 gencal_month = $*
856
857 gencal_xml = $tw_gstem/cal-$gencal_month(1)^-$gencal_month(2)^+xml
858 genarch_xml = $tw_gstem/year-links+xml
859 gencat_xml = $tw_gstem/cat-links+xml
860 gencat_mt = $tw_gstem/cat-mt+xml
861 gencat_mw = $tw_gstem/cat-mw+xml
862
863 gencal_rcs = $tw_gstem/RCS/cal-$gencal_month(1)^-$gencal_month(2)^+xml,v
864 genarch_rcs = $tw_gstem/RCS/year-links+xml,v
865 gencat_rcs = $tw_gstem/RCS/cat-links+xml,v
866 gencat_mt_rcs = $tw_gstem/RCS/cat-mt+xml,v
867 gencat_mw_rcs = $tw_gstem/RCS/cat-mw+xml,v
868
869 widget_input = $tmp_pages
870
871 widget_force = 1
872
873 . $CSA_ROOT/lib/widgets.rc
874
875 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env
876
877 # Register pingback data, if any. PLS/TPC are not only unnecessary
878 # here, but the MUST NOT be done, not to interfere with the lengthy
879 # processing of the 'queue-run pingback' processor. Note that these
880 # file contents are written partly by _userproc(_O_REQUEST) and
881 # partly by _userproc(_O_RESPONSE), so they must be moved to their
882 # final destinations only upon committing, i.e. after calling also
883 # _userproc(_O_RESPONSE).
884
885 # Queue pingbacks only if the page is publicly readable, and only
886 # if allowed. Note: if the pingback file was not created because no
887 # pingbacks were detected in the edited content, then the commit &&'
888 # chain will not complete because of such errors! That's why said file
889 # must be created with 'csaMkTemp --file'.
890
891 if (!~ $'tbl_page.p_descr' -* && ~ $'tbl_page.p_allow' $nil) {
892
893 # By testing here, pingbacks can be enabled/disabled on a per-group
894 # basis rather than globally.
895 csaTrue $TNS_PING_XREF && csaOnCommit --caller \
896 cat' '$TNS_PINGBACK_QUEUE' >> '$CSA_ROOT/var/tw-pingback-queue
897 }
898
899 # Unconditionally queue the rebuilding of sitemaps, as something was
900 # modified/deleted. This is currently no longer done since according
901 # to TypeWriter's philosophy, all publicly readable pages of a TW
902 # site are also publicly accessible through the relevant RESTful URLs.
903 # Should I provide explicit site maps in the future, I will most likely
904 # opt for more efficient incremental site maps based on Recent Changes.
905 #
906 #csaOnCommit --caller echo' '$tw_gstem' >> '$CSA_ROOT/var/tw-updates
907
908 # If parsewiki(1) mode is in effect, then both 'page+pwk' and 'page+wki'
909 # will normally end up having the same modification time from the point
910 # of view of test(1). Since this assumption may be used in the future
911 # by 'editPage' to assess whether 'page+wki' is newer than 'page+pwk',
912 # and since in case of a high machine load this may not be the case,
913 # I prefer to enforce such condition.
914
915 if (~ $'cgi.filter' parsewiki && !~ $CSA_PGM(1) CSA2) {
916 csaOnCommit touch' -m '$tw_pstem+pwk' '$tw_pstem+wki
917 }
918
919 # Toggle comments on, off or leave them unchanged for this page.
920
921 # Comments are enabled by default for new pages if appropriate.
922 csaTrue $new_page && csaTrue $TNS_CMT_ENABLE && cgi.comments = true
923
924 switch ($'cgi.comments') {
925
926 case true
927 # Set Principal Lock Semaphore(s) (PLS) (see also cmtPost).
928 csaLock $tw_gstem/recent-comment+dat || csaExit.fault
929
930 # Re-enable the previous comment table if it exists.
931 if (csaIsFullPath --exists --quiet $tw_gstem/.$'cgi.page'+cmt) {
932 csaOnCommit \
933 mv' '$tw_gstem/.$'cgi.page'+cmt' '$tw_gstem/$'cgi.page'+cmt
934 } else {
935 # Create a new comment table if it does not yet exist.
936 if (!csaIsFullPath --exists --quiet $tw_gstem/$'cgi.page'+cmt) {
937 csaOpen --fast --relaxed $tw_gstem/$'cgi.page'+cmt ||
938 csaExit.fault
939 maketable --input \
940 $CSA_ROOT/lib/comment.xrf > $CSA_RESULT ||
941 csaExit.fault 0003 maketable
942 }
943 }
944
945 case false
946 # Set Principal Lock Semaphore(s) (PLS) (see also cmtPost).
947 csaLock $tw_gstem/recent-comment+dat || csaExit.fault
948
949 # Make any previous table hidden.
950 if (csaIsFullPath --exists --quiet $tw_gstem/$'cgi.page'+cmt) {
951 csaOnCommit \
952 mv' '$tw_gstem/$'cgi.page'+cmt' '$tw_gstem/.$'cgi.page'+cmt
953 } else {
954 # Create a new hidden comment table if it does not yet exist.
955 if (!csaIsFullPath --exists --quiet $tw_gstem/.$'cgi.page'+cmt) {
956 csaOpen --fast --relaxed $tw_gstem/.$'cgi.page'+cmt ||
957 csaExit.fault
958 maketable --input \
959 $CSA_ROOT/lib/comment.xrf > $CSA_RESULT ||
960 csaExit.fault 0003 maketable
961 }
962 }
963 }
964
965 if (csaTrue $CSA_AUDIT) {
966
967 # Prevent useless versioning of most ancillary files.
968 csaTrapFile $tw_gstem/RCS/^((page+dat (recent-links
969 recent-pages recent-changes recent-headlines)^+xml)^,v)
970 }
971
972 # Set new author last, and only if available.
973 #~ $'cgi.author' - || ~ $'cgi.author' () ||
974 # csaCookie.set twauthor $'cgi.author.uri' \
975 # --expires ``($nl){date -u -d now+6month +'%a, %d-%b-%Y %H:%M:%S GMT'}
976
977 # Handle page update notifications, i.e. dispatch them to the relevant
978 # batch processor as quickly as possible, not to engage this already
979 # slowish program into even lengthier operations.
980
981 # Let's be conservative regarding what may or may not be notified.
982 if (!~ $'tbl_page.p_descr' -* && ~ $'tbl_page.p_allow' $nil) {
983
984 ~ $tmp2 /dev/null && csaMkTemp tmp2 # may still be undefined.
985
986 awktable -i $TNS_USER_TABLE -- 'BEGIN{ rc_=1 }
987 { sub(/\|.*/,"",$u_other) } # retain only 1st subfield.
988 # Note: deleted accounts are implicitly excluded by virtue of
989 # having u_other set to "x".
990 "," $u_other "," ~ /,'$'tbl_page.k_node','/ {
991 # Extract notification gateway parameters.
992 # Only e-mail notifications are supported right now.
993 gw_ = $u_other; sub(/,.*/,"",gw_)
994 if (gw_ == "") gw_ = "m"
995 printf("%s^%s ",gw_,$u_email); rc_=0
996 }
997 END { printf ("\n"); exit(rc_) }' > $tmp2
998
999 if (~ $status 0) {
1000
1001 csaLoadLib csaEmaillib.rc || csaExit.fault
1002
1003 emaillib_loaded = 1
1004
1005 if (csaIsFullPath --exists --quiet \
1006 $CSA_TPL_ROOT/tw-page-updated-email.txt) {
1007 tpl_file = tw-page-updated-email.txt
1008 } else tpl_file = (--file-root $tw_dstem tw-page-updated-email.txt)
1009
1010 # By overriding the CSA_CMD_SENDMAIL_ variable can trick the
1011 # 'csaSendMail' function into not sending mail, but rather acting
1012 # as a generic form processor.
1013
1014 CSA_CMD_SENDMAIL_ = 'cat >> '$tmp2 { csaSendMail $tpl_file }
1015
1016 csaOnCommit cp $tmp2 $CSA_ROOT/var/tw-page-updated-$'tbl_page.k_node'
1017 }
1018 }
1019
1020 # Send the client the appropriate response message if we are in RPC2 mode.
1021
1022 if (~ $CSA_PGM(1) CSA2) {
1023
1024 # Return post-id if new post.
1025 if (~ $CSA_PGM($#CSA_PGM) *.newPost) {
1026 if (~ $'cgi.numeric' ()) {
1027 CSA_RPC2_OK = \
1028 ''$CSA_LANG/$'cgi.group'/$'cgi.page'^''
1029 } else CSA_RPC2_OK = ''$'tbl_page.k_node'^''
1030 }
1031
1032 csaExit.ok
1033 }
1034
1035 # Tell the moderator (if any) about new content from *anonymous* users.
1036 # It can happen for publicly editable Wiki's.
1037 if (!csaTrue $CSA_AUTH_OK && ~ $TNS_EMAIL_MODERATOR *'@'*.*) {
1038 csaTrue emaillib_loaded || csaLoadLib csaEmaillib.rc || csaExit.fault
1039 if (csaIsFullPath --exists --quiet \
1040 $CSA_TPL_ROOT/tw-newcontent-email.txt) {
1041 tpl_file = tw-newcontent-email.txt
1042 } else tpl_file = (--file-root $tw_dstem tw-newcontent-email.txt)
1043
1044 TNS_EMAIL_TO = $TNS_EMAIL_MODERATOR
1045 tpl.var.tw.url = \
1046 $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri'
1047 csaSendMail $tpl_file
1048 }
1049
1050 # Take the client back to the updated page if we are in REST mode.
1051
1052 csaExit.ok $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri'
1053
1054 #EOF