changeset 815:66d9b07edcc2

First complete merge with featureWorld branch
author HIROSE Yuuji <yuuji@gentei.org>
date Tue, 16 Jun 2020 13:11:18 +0900
parents 70e056d2443a (current diff) 04034092338d (diff)
children de988b0a7afa
files
diffstat 9 files changed, 627 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/examples/common/default/default.css	Tue Jun 16 13:10:57 2020 +0900
+++ b/examples/common/default/default.css	Tue Jun 16 13:11:18 2020 +0900
@@ -16,6 +16,7 @@
     box-shadow: #242 2px 3px 5px;
     text-shadow: #fff 0px 0px 10px;
 }
+div.topmenu ul li.worldname {background: #eeeeff;}
 div.topmenu ul li:hover {background: #8fa;}
 div.topmenu ul a {text-decoration: none;}
 
@@ -331,6 +332,20 @@
 input[type="reset"] {margin-left: 4em;}
 
 /*
+ * World List
+ */
+li.casmenu div {display: none; position: relative; width: 200%;
+		min-width: 80%; margin-right: 0;}
+li.casmenu div table {
+    background: white; position: absolute; top: 0em; border: 3px solid navy;
+    max-width: 100%;
+}
+li.casmenu div table td {text-align: left; padding: 0.5ex 1em;}
+li.casmenu:hover div, li.casmenu:active div,
+li.casmenu div:hover, li.casmenu div:active
+{display: block;}
+
+/*
  * PR Web
  */
 body.pr {font-size: 200%;}
--- a/examples/common/default/footer.m4.html	Tue Jun 16 13:10:57 2020 +0900
+++ b/examples/common/default/footer.m4.html	Tue Jun 16 13:11:18 2020 +0900
@@ -1,6 +1,6 @@
 <p class="copyright">Driven by
 <a href="http://www.gentei.org/~yuuji/software/s4/">s4</a>
-&copy;2015-2019 by 
+&copy;2015-2020 by
 <a href="http://www.gentei.org/~yuuji/">yuuji</a>
 </body>
 </html>
--- a/examples/common/default/html.m4.html	Tue Jun 16 13:10:57 2020 +0900
+++ b/examples/common/default/html.m4.html	Tue Jun 16 13:11:18 2020 +0900
@@ -7,7 +7,7 @@
 `<meta name="theme-color" content="_S4COLOR_">',
 `<meta name="theme-color" content="#8ea">')
 <meta name="viewport" content="width=device-width,initial-scale=0.5">
-<title>_TITLE_|ifdef(`_S4NAME_',`_S4NAME_',s4)</title>
+<title>_TITLE_|ifdef(`_S4WORLD_',`{_S4WORLD_}')ifdef(`_S4NAME_',`_S4NAME_',s4)</title>
 <link rel="stylesheet" type="text/css" href="templ/default/default.css">
 <script type="text/javascript" src="s4-main.js" charset="utf-8"></script>
 ifdef(`_S4CSS_',
@@ -18,17 +18,19 @@
 <body class="_BODYCLASS_">
 <div class="topmenu">
 <ul>
- <li><a href="?home" accesskey="1" title="Shortcut: 1
-Home">ホーム</a></li>
+ <li class="casmenu"><a href="?home" accesskey="1" title="Shortcut: 1
+Home">ホーム</a>ifdef(`_S4WORLDS_',`_S4WORLDS_')</li>
  <!-- <li><a href="?blog">話題作成</a></li> -->
  <li><a href="?mems" accesskey="2" title="Shortcut: 2
 Members">参加者一覧</a></li>
- <li><a href="?grps" accesskey="3" title="Shortcut: 3
-Groups">グループ一覧</a></li>
- <li><a href="?invite" accesskey="4" title="Shortcut: 4
-Invite">招待</a></li>
+ <li class="casmenu"><a href="?grps" accesskey="3" title="Shortcut: 3
+Groups">グループ一覧</a>ifdef(`_S4WORLDGRPS_',`_S4WORLDGRPS_')</li>
+ifdef(`_S4WORLDNAME_',`',
+` <li><a href="?invite" accesskey="4" title="Shortcut: 4
+Invite">招待</a></li>')dnl
  <li><a href="?login" accesskey="5" title="Shortcut: 5
 Sign out">再ログイン</a></li>
  <!-- <li><a href="?userconf">userconf</a></li> -->
+ifdef(`_S4WORLDNAME_',`<li class="worldname">@_S4WORLDNAME_</li>',`')dnl
 </ul>
 </div>
--- a/s4-blog.sh	Tue Jun 16 13:10:57 2020 +0900
+++ b/s4-blog.sh	Tue Jun 16 13:11:18 2020 +0900
@@ -127,7 +127,7 @@
   td=`getcachedir "article/$2"`
   [ -d "$td" ] || mkdir -p $td
   tbl=${1%%[!A-Z0-9a-z_]*} rowid=${2%%[!A-Z0-9a-z_]*}
-  err blow_showentry: rowid=$rowid, '$2'=$2 user=$user
+  err blog_showentry: rowid=$rowid, '$2'=$2 user=$user
   ts=${tbl}_s tm=${tbl}_m
   at=article as=article_s am=article_m
   serial=$(($(date +%s)-1420038000))s$$
@@ -532,7 +532,7 @@
 	echo "</td></tr>"
       } > "$cachefile.$$"	######## New ROW Creation Ends here ########
       # Care about race condition
-      if [ -s $cachefile -a $cachefile -nt $cachestamp ]; then
+      if [ -z "$hte" -a -s $cachefile -a $cachefile -nt $cachestamp ]; then
 	# If other process have created cache, give up to serve our file
 	rm -f $cachefile.$$
       else
--- a/s4-funcs.sh	Tue Jun 16 13:10:57 2020 +0900
+++ b/s4-funcs.sh	Tue Jun 16 13:11:18 2020 +0900
@@ -4,20 +4,39 @@
 
 [ -f s4-config.sh ] && . ./s4-config.sh
 
+test -n "$HTTP_HOST" && isCGI=true || isCGI=false
+if $isCGI; then
+  case "$SCRIPT_NAME" in
+    *-world-*)
+      S4WORLD=${SCRIPT_NAME#*world-}
+      S4WORLD=${S4WORLD%.*}
+      echo S4WORLD=$S4WORLD >&2
+      worldconf=s4-config-${S4WORLD}.sh
+      ;;
+    *)
+      worldconf=s4-config.sh
+      ;;
+  esac
+  echo worldconf=$worldconf >&2
+  [ -n "$worldconf" -a -e "$worldconf" ] && . ./$worldconf
+  echo DB=$DB >&2
+fi
 myname=`basename ${SCRIPT_NAME:-$0}`
 mydir=`dirname ${SCRIPT_FILENAME:-$0}`
+cgiext=${CGIEXT:-.cgi}
 myargs="$@"
-test -n "$HTTP_HOST" && isCGI=true
 PATH=/usr/local/sqlite3/bin:/usr/local/vim7/bin:/usr/iekei/ImageMagick/bin:/usr/local/ImageMagick/bin:$PATH
 tmpdir=${TMPDIR:-tmp}
 dbdir=${DBDIR:-db}
+logdir=${LOGDIR:-tmp}
 tmpfiles=""
+querylog=${QUERYLOG:-$logdir/query.log}
+searchlog=${SEARCHLOG:-$logdir/search.log}
 db=${DB:-$dbdir/cgi.sq3}
-querylog=${QUERYLOG:-$tmpdir/query.log}
-searchlog=${SEARCHLOG:-$tmpdir/search.log}
+sessdb=${SESSDB:-$dbdir/sess.sq3}
+userupdateflag=$dbdir/userupdate
+sesstb=tmp.sess
 workdb=$dbdir/tmpdata.sq3
-sessdb=$dbdir/sess.sq3
-sesstb=tmp.sess
 listentlimit=${LISTENTLIMIT:-30}
 listartlimit=${LISTARTLIMIT:-50}
 admin=${ADMIN:-hostmaster@example.org}
@@ -59,16 +78,36 @@
 session=$main_session
 
 tconfs=""
-imgcached=cache/img.`date +%Y/%m`
+imgcached=cache/${S4WORLD:+$S4WORLD/}img.`date +%Y/%m`
 conftbl=_tblconf
 nl="
 "
 likeesc=`printf '\037'`		# ESCAPE char of LIKE operator
 iconcachekey="profimgcache_S"
+
+# Start debug logging
+logtag="${S4WORLD:+{$S4WORLD\}}"
+exec 3>> $logdir/debug.out
+err() {
+  echo "[`date +%F-%T%z`]$logtag $@" 1>&3
+}
 case "$HTTP_USER_AGENT" in
   *i[Pp]hone*|*[Aa]ndroid*)	touchpanel=1 ;;
   *)				touchpanel="" ;;
 esac
+
+# If S4MASTERDB is set, behave in another world
+### if [ -n "$S4MASTERDB" -a -s "$S4MASTERDB" ]; then
+# If S4WORLDLIST is set, this s4 have world!
+if [ -n "$S4WORLDLIST" ]; then
+  . ./s4-world.sh 2>> $logdir/debug.out
+  # Variables set in s4-world.sh
+  #	$S4WORLDS, $S4WROLDNAME, $S4WORLDGRPS
+  # Files created in s4-world.sh
+  #	$worldlistfile, $worldoptionfile, $worldnamefile, $worldgrpfile
+fi
+
+
 [ -f ./s4-cgi.sh ] && . ./s4-cgi.sh
 
 : <<EOF
@@ -335,10 +374,10 @@
 EOF
 
 logstart() {
-  echo "`date '+%F %T'`:[${user:-NULL}] <<<" >> ${1:-$querylog}
+  echo "`date '+%F %T'`:[${user:-NULL}]$logtag <<<" >> ${1:-$querylog}
 }
 logend() {
-  echo ">>>" >> ${1:-$querylog}
+  echo ">>>$logtag" >> ${1:-$querylog}
 }
 sqlog() {
   logstart
@@ -372,12 +411,11 @@
   #tail -f $sqi | sq $db &	# "tail -f" is too heavy. DO NOT USE!!
   sq  $db < $sqi &
   sq3pid="`jobs -p` $!"
-  if [ -n "$isCGI" ]; then
-     exec 2>> $tmpdir/error.out
+  if $isCGI; then
+     exec 2>> $logdir/error.out
   fi
-  exec 3>> $tmpdir/debug.out
   exec 5> $sqi # Turning $sqi access through fd5 for continuous open state
-  chmod o-r $tmpdir/error.out $tmpdir/debug.out
+  chmod o-r $logdir/error.out $logdir/debug.out
   rm $sqi
   # Attach supplemental DB
   cat >&5 <<-EOF
@@ -424,7 +462,11 @@
 }
 _m4() {
   #S4NAME=f,f,f
-  m4 ${S4NAME:+"-D_S4NAME_=${S4NAME}"} ${S4CSS:+-D_S4CSS_="$S4CSS"} "$@"
+  m4 ${S4NAME:+"-D_S4NAME_=${S4NAME}"} ${S4CSS:+-D_S4CSS_="$S4CSS"} \
+     ${S4WORLD:+-D_S4WORLD_="$S4WORLD"} \
+     ${S4WORLDNAME:+-D_S4WORLDNAME_="$S4WORLDNAME"} \
+     ${S4WORLDGRPS:+-D_S4WORLDGRPS_="$S4WORLDGRPS"} \
+     ${S4WORLDS:+-D_S4WORLDS_="$S4WORLDS"} "$@"
 }
 ismember() {
   # $1=user, $2=group
@@ -1216,6 +1258,7 @@
   for kv in `echo $HTTP_COOKIE|sed 's/[;, ]/ /g'`; do
     k="${kv%%=*}"
     v="`echo ${kv#*=}|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
+    ## err "GetCookie: $k=[$v]"
     case "$k" in
       user)	_user="$v" ;;
       skey)	_skey="$v" ;;
@@ -1235,7 +1278,7 @@
   # smail rcpts subj (file)
   # $SMAIL_TO  <- Recipient value of To: header
   # $MAIL_FROM <- From: header value
-  from=`echo "${MAIL_FROM:-$admin}"|nkf -jM|tr -d '\n'`
+  from=`echo "${MAIL_FROM:-$admin}"|nkf -jM|tr : /|tr -d '\n'`
   rcpt=`echo $1|tr ' ' '\n'|sort -u|tr '\n' ' '` # uniq and strip newlines
   rcptheader=`echo $1|tr ' ' '\n'|sort -u|sed '2,$s/^/To: /g'`
   subj=`echo $2|nkf -jM|tr -d '\n'`
@@ -1345,7 +1388,7 @@
   newpswd=`genrandom` # newsalt=`genrandom 5`
   #encpswd=`mycrypt "$newpswd" "$newsalt"`
   encpswd=`echo $newpswd|mypwhash`
-  dbsetbyid user $user pswd "$encpswd"
+  dbsetbyid user $user pswd "$encpswd" && touch $userupdateflag
   # Avoid $user substitution with m4, because $url comes from user input.
   _m4 -D_PSWD_="$newpswd" -D_URL_="$url" -D_ADMIN_="$admin" \
      $msgdir/mail-newaccount.m4 \
@@ -1420,9 +1463,6 @@
 trap cleanup INT HUP EXIT TERM PIPE
 # trap cleanup INT HUP
 
-err() {
-  echo "[`date +%F-%T%z`] $@" 1>&3
-}
 
 cgiinit() {
   tmpd=`tmpd=$tmpdir mktempd`
@@ -1827,10 +1867,38 @@
   # GF_ACTION="?grp+$1" edittable "$formdir/grp.def" "grp" "$rowid" #2015-0804
   GF_STAGE="groupupdate" edittable "$formdir/grp.def" "grp" "$rowid"
   if [ -z "$STOPCLONEMSG" ]; then
-    html div 'class="fold"' <<-EOF
-	`cgi_checkbox clone yes id="clone"`<label
-	 for="clone">同一メンバーで別グループを作成する</label>
-	<div>
+    ## Setup migration menu
+    height="10em"		## Ugly!!
+    if [ -n "$S4WORLDLIST" ]; then
+      v=`fgrep -v "value=\"$worldconf\"" $worldoptionfile`
+      err v=$v
+      if [ -n "$v" ]; then
+	migrate=$(cat<<-EOF
+	`cgi_radio grpaction migrate id="migrate"`<label
+	 for="migrate">別Worldへ移住</label>
+	<div style="height: $height;">
+	<form action="?migrategrp">
+	<p>移住先:<select name="migrateto">$nl$v$nl</select></p>
+	<p>グループや掲示板のURLが変わります。
+	外部からリンクしている場合は飛べなくなります。
+	すでにリンクされた掲示板を多数含む場合は既存グループを温存し、
+	「グループのクローン」で
+	メンバーを引き継いだ上でそのクローンを移住するのがお勧めです。</p>
+	<p><label>`cgi_checkbox emichk yes`確認</label></p>
+	`cgi_hidden stage migrategrp`
+	`cgi_hidden rowid $rowid`
+	`cgi_submit OK`
+	`cgi_reset Reset`
+	</form>
+	</div>
+	EOF
+	       )
+      fi
+    fi
+    html div 'class="foldtabs"' <<-EOF
+	`cgi_radio grpaction clone id="clone"`<label
+	 for="clone">グループのクローン作成</label>
+	<div style="height: $height;">
 	 <p>構成メンバーが同じ新規グループを作成します。</p>
 	 <table>
 	  <tr><td><a href="?groupclone+$rowid">
@@ -1843,9 +1911,55 @@
 	 <p>ボタンを押すと即作成します。不要な場合はグループ編集で
 	 削除してください。</p>
 	</div>
+	$migrate
+	 `cgi_radio grpaction close id="x"`<label for="x" accesskey="x">×</label>
+	<div style="height: $height; background: transparent;"></div>
 	EOF
   fi
 }
+migrategrp() {
+  rowid=`getpar rowid`
+  rowid=${rowid%%[!0-9]*}
+  grp=`getgroupbyid $rowid`
+  if ! isgrpowner "$user" "$grp"; then
+    echo "<p><a href=\"?grp+$rowid\">`echo "$grp"|htmlescape`</a></p>"
+    return
+  fi
+  if [ x`getpar emichk` != x"yes" ]; then
+    echo "移住確認未チェックなので中止します。" | html p
+    grp "$rowid"
+    return
+  fi
+  destconf=`getpar migrateto`
+  err destconf=$destconf
+  if [ ! -e $destconf ]; then
+    echo "移住先Worldが認識できないので中止します($destconf)。" | html p
+    grp "$rowid"
+    return
+  fi
+  if [ -n "$worldconf" ]; then
+    srcconf=$worldconf
+  else
+    srcconf=s4-config.sh
+  fi
+  _m4 -D_TITLE_="移住操作" -D_BODYCLASS_="" $layout/html.m4.html
+  echo "移住操作" | html h1
+  echo '<pre>'
+  set -- "$srcconf" "$destconf" "$rowid"
+  err  ./s4-migrate.sh "$srcconf" "$destconf" "$rowid"
+  . ./s4-migrate.sh		# Dot(.) sourcing might not pass arguments
+  rc=$?
+  echo "</pre>"
+  if [ $rc -eq 0 ]; then
+    echo "World [$world] への移住完了。" | html p
+    echo "<p><a href=\"$dsturl?grp+$destrowid\">移住先</a></p>"
+    clean-orphaned
+  else
+    echo "移住失敗" | html p
+    echo "移動先に重複がないか確認して下さい。" | html p
+  fi
+  return
+}
 mems() {
   _m4 -D_TITLE_="参加者一覧" -D_BODYCLASS_=listmember $layout/html.m4.html
   kwd=`getpar kwd`
@@ -2067,8 +2181,14 @@
   cond="gname in (select gname from grp_mem where user='$uname')"
   search_form_args=""
   if [ x"$user" = x"$uname" ]; then
-    usermenu="<a href=\"?userconf\" accesskey=\"e\"
-	 title=\"Shortcut: E${nl}Edit Profile\">プロフィールの編集</a> /
+    if [ -z "$S4MASTERDB" ]; then
+      usermenu="<a href=\"?userconf\" accesskey=\"e\"
+	 title=\"Shortcut: E${nl}Edit Profile\">プロフィールの編集</a> / "
+    elif [ -n "$S4MASTERURL" ]; then
+      usermenu="<a href=\"$S4MASTERURL\" accesskey=\"e\"
+	 title=\"Shortcut: E${nl}Main Site\">Base World</a> / "
+    fi
+    usermenu="$usermenu
 	<a href=\"?blog\" accesskey=\"n\" title=\"Shortcut: N${nl}New blog\">新規話題の作成</a>"
     # Display folders
     sql="select count(id) from article_m where id
@@ -2091,7 +2211,8 @@
 
   tf=$tmpd/title.$$ pf=$tmpd/profile.$$ bf=$tmpd/blogs.$$ sf=$tmpd/search.$$
   search_form "$search_form_args"	> $sf
-  printf "%s さん" "$gecos"|htmlescape	> $tf
+  printf "%s さん%s" "$gecos" "${S4WORLDNAME:+@$S4WORLDNAME}" \
+    | htmlescape	> $tf
   { echo "<div class=\"noprofimg\">"
     viewtable $formdir/user.def user $1
     echo "</div>"
@@ -2390,7 +2511,7 @@
   # Note that mtime is stored only in grp_s.
 ## err LE:sql.1="$sql"
   total=`query "with x as ($sql) select count(*) from x;"`
-  echo "${entity} 一覧" | html h2
+  echo "${entity} 一覧" "${S4WORLDNAME:+@$S4WORLDNAME}" | html h2
   echo '<div class="listentry">'		# List-entry div
   # Show owner/member filter button
   METHOD=GET
@@ -3654,6 +3775,26 @@
   [ "$ddd" ] && err "----- `gdate +%FT%T.%3N` ------------666666"
 }
 
+clean-orphaned() {
+  # This shoud be done by foreign_key rules, but some db lack them
+  query<<-EOF
+	-- Find blogs that have no parent
+	WITH orphanedblog AS (
+	  SELECT blog.id,val FROM blog JOIN blog_s bs
+	         ON blog.id=bs.id AND key='owner'
+              WHERE val NOT IN (SELECT gname FROM grp)
+                AND val NOT IN (SELECT name FROM user)
+	) -- Remove them
+	DELETE FROM blog WHERE id IN (SELECT id FROM orphanedblog);
+
+	-- Find articles that have no parent blog
+	WITH orphanedarticle AS (
+	  SELECT id FROM article
+	  WHERE  blogid NOT IN (SELECT id FROM blog)
+	) -- Remove them
+	DELETE FROM article WHERE id IN (SELECT id FROM orphanedarticle);
+	EOF
+}
 par2table() (
   # copy current parameters of par into destination table
   # $1=definition-file
@@ -3708,6 +3849,9 @@
     if [ x"$rm" = x"yes" ]; then
       if [ x"$rm$cfm" = x"yesyes" ]; then
 	query "delete from $tbl where rowid=$rowid;"
+	if [ x"$tbl" = x"grp" -o x"$tbl" = x"blog" ]; then
+	  clean-orphaned
+	fi
 	return 4
       else
 	echo "消去確認のチェックがないので消さなかったの..." | html p
@@ -3960,8 +4104,10 @@
 	.read $transaction
 	RELEASE SAVEPOINT pa2table_insert;
 	EOF
-  return $?
-  ##err donee
+  rc=$?
+  [ $rc -eq 0 -a x"$tbl" = x"user" ] && touch $userupdateflag
+  ## err "Table:$tbl update done "
+  return $rc
 )
 genform() {
   # $1 = form definition file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/s4-migrate.sh	Tue Jun 16 13:11:18 2020 +0900
@@ -0,0 +1,201 @@
+#!/bin/sh
+# s4 - migration module
+# (C)2020 by HIROSE, Yuuji
+
+srcdb=`unset DB; [ -f $1 ] && . ./$1 && echo ${DB:-db/cgi.sq3}`
+dst=`unset DB; [ -f $2 ] && . ./$2 && echo "${DB:-db/cgi.sq3}|$URL"`
+dstdb=${dst%\|*}
+dsturl=${dst##*\|}
+type htmlescape >/dev/null 2>&1 || . `dirname $1`/s4-funcs.sh
+case "$2" in
+  s4-config.sh) world=Base ;;
+  *)		world=${2##*-config-}; world=${world%.*} ;;
+esac
+htmlworld=`echo "$world"|htmlescape`
+
+err "--- Migration Started with \$1=$1 \$2=$2 at `date` ---"
+err srcdb=$srcdb dstdb=$dstdb
+err URL=$URL
+err dstURL=$dsturl
+
+shift 2
+
+query "ATTACH DATABASE \"$dstdb\" AS dst;" || abort "Cannot attach db #{dstdb}"
+
+failure=0
+for grid; do
+  grid=$((0 + $grid))
+  gnamesql="(SELECT gname FROM main.grp WHERE rowid=$grid)"
+  grp=`query "SELECT gname FROM main.grp WHERE rowid=$grid;"`
+  htmlgrp=`echo "$grp"|htmlescape`
+  qgrp=`sqlquote "$grp"`
+  if [ -n "`query \"SELECT gname FROM dst.grp WHERE gname=$qgrp;\"`" ]; then
+    echo "[$htmlgrp]グループがWorld[$htmlworld]にあるので中止します。"|html p
+    failure=$((failure+1))
+    continue
+  fi
+  echo "Copying $grid..."
+  query "BEGIN;"
+  query "REPLACE INTO dst.grp SELECT * FROM main.grp WHERE rowid=$grid;"
+  destrowid=`query "SELECT last_insert_rowid();"`
+  for tbl in grp_s grp_m grp_mem grp_mem_s grp_mem_m \
+		   grp_adm grp_adm_s grp_adm_m; do
+    query "REPLACE INTO dst.$tbl SELECT * FROM main.$tbl
+	   WHERE gname=$gnamesql;"
+  done
+  blogs=`query "SELECT group_concat(\"'\"||id||\"'\", ',')
+  	        FROM main.blog_s WHERE key='owner' AND val=$gnamesql;"`
+  echo blogs=$blogs
+  for tbl in blog blog_s blog_m; do
+    query <<-EOF
+	REPLACE INTO dst.$tbl
+	  SELECT * FROM main.$tbl
+	  WHERE id IN ($blogs);
+	EOF
+  done
+  for tbl in article article_s article_m; do
+    query <<-EOF
+	REPLACE INTO dst.$tbl
+	  SELECT * FROM main.$tbl
+	  WHERE id IN (SELECT id FROM main.article WHERE blogid IN ($blogs))
+	  ORDER BY rowid;
+	EOF
+  done
+  ## Check the equality of two DBs
+  echo "grid=$grid grp=$grp qgrp=$qgrp" | htmlescape
+  # grp
+  d1=$(query <<-EOF
+	SELECT * FROM main.grp
+	       	 NATURAL LEFT JOIN main.grp_s
+		 NATURAL LEFT JOIN main.grp_m
+	WHERE gname=$qgrp
+	EXCEPT
+	SELECT * FROM dst.grp
+	       	 NATURAL LEFT JOIN dst.grp_s
+		 NATURAL LEFT JOIN dst.grp_m
+	WHERE gname=$qgrp;
+	EOF
+	)
+  err DONE 
+  err d1="$d1"
+  # blog
+  d2=$(query <<-EOF
+	SELECT * FROM main.blog
+	       	 NATURAL LEFT JOIN main.blog_s
+		 NATURAL LEFT JOIN main.blog_m
+	WHERE id IN (SELECT id FROM main.blog_s
+	      	     WHERE key='owner' AND val=$qgrp)
+	EXCEPT
+	SELECT * FROM dst.blog
+	       	 NATURAL LEFT JOIN dst.blog_s
+		 NATURAL LEFT JOIN dst.blog_m
+	WHERE id IN (SELECT id FROM dst.blog_s
+	      	     WHERE key='owner' AND val=$qgrp);
+	EOF
+    )
+  err d2="$d2"
+  # article
+  d3=$(query <<-EOF
+	SELECT * FROM main.article
+	       	 NATURAL LEFT JOIN main.article_s
+	       	 NATURAL LEFT JOIN main.article_m
+	WHERE blogid IN ($blogs)
+	EXCEPT
+	SELECT * FROM dst.article
+	       	 NATURAL LEFT JOIN dst.article_s
+	       	 NATURAL LEFT JOIN dst.article_m
+	WHERE blogid IN ($blogs);
+	EOF
+       )
+  err d3="$d3"
+  if [ -z "$d1$d2$d3" ]; then
+    echo "Copying done, rewriting article links..."
+    echo "Old URL: $URL"
+    echo "New URL: $dsturl"
+    query <<-EOF
+	UPDATE dst.article_s
+	SET val=replace(val,
+			'${URL}?grp+$grid',
+			'${dsturl}?grp+$destrowid')
+	WHERE key='text' AND val LIKE '%${URL}%';
+	EOF
+    # Create blog-rowid conversion table
+    sedfile=$tmpd/arttrans.sed
+    # sedfile=tmp/arttrans.sed
+    query <<-EOF > $sedfile
+	WITH arttrans AS (
+	  SELECT s.rowid srcrid, d.rowid dstrid
+	  FROM main.article s JOIN dst.article d ON s.id=d.id
+	  WHERE s.id in (SELECT id
+	  	     	 FROM article WHERE blogid IN ($blogs))
+	) SELECT printf("/^>/s/\#%s($|[^0-9])/\#%s\1/g", srcrid, dstrid)
+	  FROM arttrans;
+	EOF
+    query <<-EOF > $tmpd/repl.art.rowid
+	SELECT rowid FROM dst.article_s
+	WHERE key='text' AND val GLOB '>*#[1-9]*'
+	  AND id IN (SELECT id FROM article WHERE blogid IN ($blogs));
+	EOF
+    sql=$tmpd/update.sql
+    for arid in `cat $tmpd/repl.art.rowid`; do
+      newval=`query "SELECT hex(val) FROM dst.article_s WHERE rowid=$arid;" \
+              | unhexize | sed -Ef "$sedfile" | hexize`
+      echo "UPDATE dst.article_s SET val=X'$newval' WHERE rowid=$arid;"
+    done >$sql
+    # Rewrite blog-links in the group
+    #  Create sed script
+    sedfile2=${sedfile}2
+    query <<-EOF > $sedfile2
+	WITH blogtrans AS (
+	  SELECT s.rowid srcrid, d.rowid dstrid
+	  FROM main.blog s JOIN dst.blog d ON s.id=d.id
+	  WHERE s.id IN ($blogs)
+	) SELECT printf('s/(\?replyblog)\+%s($|[^0-9])/\1+%s\2/g',
+		         srcrid, dstrid)
+	  FROM blogtrans;
+	EOF
+    bloglinks=$tmpd/bloglinks.rowid
+    query <<-EOF > $bloglinks
+	SELECT rowid FROM dst.article_s
+	WHERE key='text' AND val LIKE '%?replyblog+%'
+	  AND id IN (SELECT id FROM article WHERE blogid IN ($blogs));
+	EOF
+    for arid in `cat $bloglinks`; do
+      newval=`query "SELECT hex(replace(val, '$URL', '$dsturl')) 
+      		     FROM dst.article_s WHERE rowid=$arid;" \
+              | unhexize | sed -Ef "$sedfile2" | hexize`
+      echo "UPDATE dst.article_s SET val=X'$newval' WHERE rowid=$arid;" >>$sql
+    done
+    if [ -z "`query \".read $sql\"`" ]; then
+      query <<-EOF
+	DELETE FROM main.article WHERE blogid IN ($blogs);
+	DELETE FROM main.blog WHERE id IN ($blogs);
+	DELETE FROM main.grp WHERE rowid=$grid;
+	EOF
+      s=`query "SELECT * FROM main.grp WHERE rowid=$grid;"`
+      if [ -z "$s" ]; then
+	echo "Success!!"
+	query "END;"
+	clean-orphaned
+	echo "Done."
+      else
+	echo Removal failed
+	echo "現行グループ消去ができませんでした。"
+	echo "書き込みの多いグループの場合は空いている時間帯に試して下さい。"
+	query "ROLLBACK;"
+	failure=-2
+      fi
+    else
+      failure=-1
+      echo "Replacing failed."
+      query "ROLLBACK;"
+    fi
+  else
+    failure=$((failure + 1))
+    echo "Fail!"
+    query "ROLLBACK;"
+  fi
+done
+
+err "Migration ended at `date` with failure=$failure"
+return $failure
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/s4-newworld.sh	Tue Jun 16 13:11:18 2020 +0900
@@ -0,0 +1,86 @@
+#!/bin/ksh
+#!/bin/sh
+# Create New Wolrd in s4
+# Arguments:
+#   $1 = World Display Name in UTF-8
+#   $2 = World ShortName in alpha-numeric
+#   $3 = World Description in UTF-8
+. `dirname $0`/s4-config.sh
+if ! type htmlescape >/dev/null 2>&1; then
+  . `dirname $0`/s4-funcs.sh ### > /dev/null 2>&1
+  trap 'exit 1' INT QUIT
+fi
+dispname=$1
+shortname=$2
+desc=$3
+
+readvar() {	# $1=varname $2=PromptString
+  echo -n "${2:-$1=:} "
+  read $1
+}
+
+echo db=$db URL=$URL isCGI=$isCGI
+if ! $isCGI; then
+  while true; do
+    dispname=`echo $dispname | tr -d ': \t\n"' | fold -w 28 | head -1`
+    test -n "$dispname" && break
+    readvar dispname "分かりやすいWorld名14字以内"
+  done
+  while true; do
+    shortname=`echo $shortname | tr -c -d '_0-9A-Za-z.-' | colrm 11`
+    test -n "$shortname" && break
+    readvar shortname "英数字のみ10字以内のWorldシンボル(URLの一部)"
+  done
+  while true; do
+    desc=`echo $desc | tr -d ': \t\n"'`
+    test -n "$desc" && break
+    readvar desc "概要(どのような基準でこのWorldを使うかなど)"
+  done
+fi
+echo "wl=$S4WORLDLIST"
+echo "$dispname:$shortname:$desc"
+newworld=$(
+  { echo "$S4WORLDLIST" | tr ' ' '\n' \
+      | awk -F: "\$2 != \"$shortname\"{print}"
+    echo "$dispname:$shortname:$desc"
+  } | tr '\n' ' ' | tr -d '"'
+	)
+if [ -z "$newworld" ]; then
+  exit
+fi
+
+# Create config
+bgcolor=$(
+  od -An -tu1 -N3 < /dev/urandom \
+    | { read r g b
+	r=$((192+r/4)); g=$((192+g/4)); b=$((192+b/4))
+	printf "#%x%x%x" $r $g $b
+  })
+cat<<-EOF > s4-config-$shortname.sh
+	S4MASTERURL=\$URL
+	URL=`dirname ${URL}.`/s4-world-$shortname$cgiext
+	S4COLOR="$bgcolor"			# Change this!
+	DB=$dbdir/$shortname.sq3
+	SESSDB=$dbdir/sess.sq3
+	S4MASTERDB=$db
+	S4CSS=$shortname.css
+	TMPDIR=$tmpdir/$shortname
+EOF
+# Create CSS
+cat<<-EOF > $shortname.css
+	body {background: $bgcolor;}
+	body.moderated {background: $bgcolor; border: 3px gold solid;}
+EOF
+mkdir -m 1775 $tmpdir/$shortname
+# Update s4-config.sh
+cat<<-EOF | ed s4-config.sh
+	g/^S4WORLDLIST=/d
+	\$a
+	S4WORLDLIST="`echo $newworld`"
+	.
+	wq
+EOF
+DB=db/$shortname.sq3 `dirname $0`/s4-init.sh
+(S4MASTERDB=$db; db=db/$shortname.sq3; . ./s4-world.sh)
+(cd `dirname $0`; ln -s s4$cgiext s4-world-$shortname$cgiext)
+echo $newworld added
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/s4-world.sh	Tue Jun 16 13:11:18 2020 +0900
@@ -0,0 +1,138 @@
+#!/bin/sh
+
+# Setup variables for running time
+if $isCGI; then
+  case "$S4WORLDLIST" in
+    *:*:*)
+      worldlistfile=cache/worldlist
+      worldgrpfile=cache/worldgrps
+      worldoptionfile=cache/worldoption
+      worldnamefile=cache/worldname
+      if [ ! -e $worldlistfile -o $worldlistfile -ot s4-config.sh \
+	     -o ! -e $worldoptionfile -o $worldoptionfile -ot s4-config.sh \
+	     -o s4-world.sh -nt $worldoptionfile ]
+      then
+	echo S4MASTERURL=$S4MASTERURL >> tmp/debug.out
+	cat <<-EOF > $worldlistfile
+	<div><table>
+	 <tr><th>World List</th></tr>
+	 <tr><td title="Base World$nl拠点となるWorldです">&rArr;
+	  <a href="${S4MASTERURL:-$URL}">Base</a></td></tr>
+	EOF
+	true > $worldoptionfile
+	for i in $S4WORLDLIST; do
+	  echo $i | {
+	    IFS=: read name short d
+	    cgi="s4-world-$short$cgiext"
+	    conf="s4-config-$short.sh"
+	    cat<<-EOF >>$worldlistfile
+	    <tr><td title="$d">&rArr; <a href="$cgi">$name</a></td></tr>
+		EOF
+	    cat<<-EOF >>$worldoptionfile
+	    <option title="$d" value="$conf">$name</option>
+		EOF
+	    echo "$name" > $worldnamefile.$short
+	  }
+	done
+	if [ -s "$worldoptionfile" ]; then
+	  echo "<option value=\"s4-config.sh\">Base</option>" >> $worldoptionfile
+	  echo "</table>$nl</div>" >> $worldlistfile
+	  sed 's/href="\([^>]*\)"/href="\1?grps"/' $worldlistfile \
+	      > $worldgrpfile
+	else
+	  true > $worldoptionfile; true > $worldlistfile	# Remove contents
+	  rm -f ${worldnamefile}.*
+	fi
+      fi
+      if [ -s "$worldlistfile" ]; then
+	S4WORLDS="▼spaste(\`$worldlistfile')"
+	S4WORLDNAME=${S4WORLD:+`cat $worldnamefile.$S4WORLD`}
+	S4WORLDGRPS="▼spaste(\`$worldgrpfile')"
+      fi
+      ;;
+  esac
+fi
+
+err "db=$db mas=$S4MASTERDB sessdb=$sessdb"
+# If in parent world, no need to do rest of jobs
+if [ -z "$S4MASTERDB" -o ! -s "$S4MASTERDB" ]; then
+  return
+fi
+# Confim child
+if [ "$db" -ef "$S4MASTERDB" ]; then
+  return		# Points to the same file
+fi
+
+# Now Another world is ACTIVE
+# sessdb=`dirname $S4MASTERDB`/sess.sq3
+## skey="skey-`basename $mydir`"
+syncflag=${db%.*}.synctime
+runflag=${db%.*}.run
+userupdateflag=`dirname $S4MASTERDB`/`basename $userupdateflag`
+test ! -e "$userupdateflag"		&& return
+test "$syncflag" -nt "$userupdateflag"	&& return
+if [ -s "$runflag" ]; then
+  limit=`cat $runflag|tr -c -d 0-9`
+  if [ -n "$limit" -a "$limit" -gt `date +%s` ]; then
+    err "World $S4WORLD account sync withholded by process $$"
+    return		# Running sync by other process not leaching timeout
+  fi
+fi
+echo $((`date +%s` + 10)) > $runflag	# Setting running flag by timeout 10s
+
+#  for sub.sq3
+# 
+#  main: user: 'taro', 'hanako', 'shige'
+#  sub:  user: 'taro', 'hanako', 'shige'
+#  sub2: user_s: ('taro', 't'), ('hanako', 'h'), ('shige', 's')
+#  then update
+# 
+
+## sqlite3 -cmd '.timer 1' -cmd '.echo 1' $db <<EOF
+err "`gdate +%S.%3N` Starting account synchronization[$$]"
+## cat > tmp/sql <<EOF
+num=$(sqlite3 -bail -cmd 'PRAGMA FOREIGN_KEYS=on' $db <<EOF
+.timeout 1000
+ATTACH DATABASE "$S4MASTERDB" AS m;
+CREATE TABLE IF NOT EXISTS user(name, primary key(name));
+BEGIN;
+DElETE FROM main.user WHERE rowid NOT IN (SELECT rowid FROM m.user);
+INSERT INTO main.user(rowid, name)
+       SELECT rowid, name FROM m.user
+       WHERE m.user.rowid NOT IN (SELECT rowid FROM user);
+UPDATE user SET name = (SELECT name FROM m.user WHERE main.user.rowid=m.user.rowid);
+DELETE FROM main.user_s WHERE rowid NOT IN (SELECT rowid FROM m.user_s);
+REPLACE INTO main.user_s(rowid, name, key, type, val, bin)
+	SELECT rowid,* FROM m.user_s;
+DELETE FROM main.user_m WHERE rowid NOT IN (SELECT rowid FROM m.user_m);
+REPLACE INTO main.user_m(rowid, name, key, type, val, bin)
+	SELECT rowid,* FROM m.user_m
+	WHERE key NOT LIKE '%cache%';
+END;
+
+/* Compare user tables */
+WITH master AS (
+  SELECT p.rowid,* FROM m.user p
+  	 NATURAL LEFT JOIN m.user_s
+	 NATURAL LEFT JOIN m.user_m
+), thisworld AS (
+  SELECT p.rowid,* FROM user p
+  	 NATURAL LEFT JOIN user_s
+ 	 NATURAL LEFT JOIN user_m
+), m_a AS (
+  SELECT * FROM master EXCEPT SELECT * FROM thisworld
+), a_m AS (
+  SELECT * FROM thisworld EXCEPT SELECT * FROM master
+) SELECT (SELECT count(*) FROM m_a) + (SELECT count(*) FROM a_m);
+DETACH DATABASE m;
+EOF
+)
+### num=$(sqlite3 -bail -cmd 'PRAGMA FOREIGN_KEYS=on' $db < tmp/sql )
+if [ -n "$num" -a $num -eq 0 ]; then
+  err "`gdate +%S.%3N` Account synchronization[$$] done in difference $num"
+  echo "`date '+%F %T'`: Sync done by process $$" >> $syncflag
+else
+  err "Account synch[$$] failed or bailed with num=[$num]"
+fi
+test -e "$runflag" && rm -f "$runflag"
+return $num
--- a/scripts/s4-sns.case	Tue Jun 16 13:10:57 2020 +0900
+++ b/scripts/s4-sns.case	Tue Jun 16 13:11:18 2020 +0900
@@ -19,7 +19,7 @@
     showattc "$@"
     exit 0			# Do not output further chunks
     ;;
-  invite|groupman|userconf|groupconf|mems|grps|grp|groupupdate|groupclone|grpaction|joingrpadmit|commission|editheading|editart|showattc|send2mem|mvart)
+  invite|groupman|userconf|groupconf|mems|grps|grp|groupupdate|groupclone|grpaction|joingrpadmit|commission|editheading|editart|showattc|send2mem|mvart|migrategrp)
     contenttype; echo
     [ -n "$1" ] && shift
     $stage "$@"

yatex.org