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	#  '<string>'$CSA_LANG/$'cgi.group.literal'/$'cgi.page.literal'^'</string>'
    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/\((:[^)]\+:)\)/<!-- \1 -->/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/ *\(&#160;\) */\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/\((:[^)]\+:)\)/<!-- \1 -->/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/ *\(&#160;\) */\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 '<meta name="DC.title" lang="$[CSA_LANG_ISO:x]"' \
   564		'content="$[tpl.var.html.title:x]" />' > $tmp1
   565	
   566	   # The geo.placename attribute is currently hard-wired to page name.
   567	   echo '<meta name="geo.placename" lang="$[CSA_LANG_ISO:x]"' \
   568			'content="$[tpl.var.html.title:x]" />' >> $tmp1
   569	
   570	   if (~ $'cgi.tags.xml' *[a-z]*) {
   571	
   572	      #echo '<meta name="keywords" lang="$[CSA_LANG_ISO:x]"' \
   573	      #  'content="'$'cgi.tags.xml'^'" />' >> $tmp1
   574	
   575	      # keywords should rather appear in this other way.
   576	      {
   577	      	 echo -n '<meta name="keywords" lang="$[CSA_LANG_ISO:x]" '
   578		 echo -n 'content="'$'cgi.tags.xml' | sed 's/ /, /g;s/_/ /g'
   579		 echo '" />'
   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 '<meta name="ICBM"' \
   590		'content="'$'cgi.tags.geo'(1)^', '$'cgi.tags.geo'(2)^'" />' >> $tmp1
   591	      echo '<meta name="geo.position"' \
   592		'content="'$'cgi.tags.geo'(1)^';'$'cgi.tags.geo'(2)^'" />' >> $tmp1
   593	
   594	      ~ $'cgi.tags.geo'(3) () ||
   595		echo '<meta name="geo.region"' \
   596			'content="'$'cgi.tags.geo'(3)^'" />' >> $tmp1
   597	   }
   598	
   599	   ~ $'tpl.var.tw.author' () ||
   600	     echo '<meta name="author" lang="$[CSA_LANG_ISO:x]"' \
   601			'content="$[tpl.var.tw.author:x]" />' >> $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 <subcat>-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 <subcat>-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 <subcat>-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		 '<string>'$CSA_LANG/$'cgi.group'/$'cgi.page'^'</string>'
  1029	      }  else CSA_RPC2_OK = '<int>'$'tbl_page.k_node'^'</int>'
  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