s4

view s4-funcs.sh @ 836:b3516ee2bb4d

Filename renaming added to article EDIT
author HIROSE Yuuji <yuuji@gentei.org>
date Tue, 23 Jun 2020 12:58:31 +0900
parents f3d422665ea8
children a6462eea48be
line source
1 #!/bin/sh
2 # Here's global variable table. Do not use this names.
3 # $HGid$
5 [ -f s4-config.sh ] && . ./s4-config.sh
7 test -n "$HTTP_HOST" && isCGI=true || isCGI=false
8 if $isCGI; then
9 case "$SCRIPT_NAME" in
10 *-world-*)
11 S4WORLD=${SCRIPT_NAME#*world-}
12 S4WORLD=${S4WORLD%.*}
13 echo S4WORLD=$S4WORLD >&2
14 worldconf=s4-config-${S4WORLD}.sh
15 ;;
16 *)
17 worldconf=s4-config.sh
18 ;;
19 esac
20 echo worldconf=$worldconf >&2
21 [ -n "$worldconf" -a -e "$worldconf" ] && . ./$worldconf
22 echo DB=$DB >&2
23 fi
24 myname=`basename ${SCRIPT_NAME:-$0}`
25 mydir=`dirname ${SCRIPT_FILENAME:-$0}`
26 cgiext=${CGIEXT:-.cgi}
27 myargs="$@"
28 PATH=/usr/local/sqlite3/bin:/usr/local/vim7/bin:/usr/iekei/ImageMagick/bin:/usr/local/ImageMagick/bin:$PATH
29 tmpdir=${TMPDIR:-tmp}
30 dbdir=${DBDIR:-db}
31 logdir=${LOGDIR:-tmp}
32 tmpfiles=""
33 querylog=${QUERYLOG:-$logdir/query.log}
34 searchlog=${SEARCHLOG:-$logdir/search.log}
35 defaultdb=$dbdir/cgi.sq3
36 db=${DB:-$defaultdb}
37 sessdb=${SESSDB:-$dbdir/sess.sq3}
38 userupdateflag=$dbdir/userupdate
39 sesstb=tmp.sess
40 workdb=$dbdir/tmpdata.sq3
41 listentlimit=${LISTENTLIMIT:-30}
42 listartlimit=${LISTARTLIMIT:-50}
43 admin=${ADMIN:-hostmaster@example.org}
44 noreply=${NOREPLY:-noreply@example.org}
45 noreply_from="${S4NAME:-s4} message notification <$noreply>"
46 invite_policy=${INVITE_POLICY:-"このコミュニティに関りのあるあなたの信頼できる人を招きます。"}
47 templ=${TEMPL:-templ}
48 layout=${LAYOUT:-$templ/default}
49 formdir=${FORMDIR:-$templ/form}
50 imgdir=${IMGDIR:-img}
51 url=${URL:-"${REQUEST_SCHEME:-http${HTTPS:+s}}://$HTTP_HOST$REQUEST_URI"}
52 urlbase=${url%%\?*}
53 msgdir=$templ/msg
54 timeout="+2 days"
55 memoplimitdays="7"
56 dumpcollen=22
57 #thumbxy=120x120
58 thumbxy=96x96
59 iconxy_S=80x80
60 iconxy_M=400x400
61 maximagexy=1600x1600
62 ### maximagexy=400x400
63 filesize_max=$((5*1024*1024))
64 filesize_max_MB="$((filesize_max/1024/1024))MB"
65 file_accept='accept="image/*,text/*,audio/*,application/vnd.oasis.*,application/pdf,application/x-*"'
66 file_accept='accept=".jpg,.jpeg,.gif,.png,.tiff,.pdf,.odt,.ods,.odp,.odg,.mp3,.mp4,.avi,.ogg,.mov,.webm,.gpx,.json,.geojson,.kml,.html,.css,.rb,.c,.txt,.zip,.xcf,.bz2,.gz,.xz,.7z,.csv,.dat"'
67 file_accept_egrep='^(text/|message/|image/|audio/|video/|application/(vnd.oasis|pdf|epub|xml|zip|[xz]-))'
68 file_accept_help="
69 添付可能ファイル: テキスト、画像、音声、動画、ODF、PDF、
70 圧縮ファイル、データベースファイル
71 (いずれも ${filesize_max_MB} 以内)
72 "
73 file_warn="$file_accept_help
74 [編集]リンクから修正してください。"
75 blogreadflagrowid=0
76 blogcutoffflagrowid=-1
77 nonewgroupworld=${NONEWGROUPWORLD:-*archive*}
78 whatsnewdays=${WHATS_NEW_DAYS:-14}
79 main_session=`date +%F-$$`
80 session=$main_session
82 tconfs=""
83 imgcached=cache/${S4WORLD:+$S4WORLD/}img.`date +%Y/%m`
84 conftbl=_tblconf
85 nl="
86 "
87 likeesc=`printf '\037'` # ESCAPE char of LIKE operator
88 iconcachekey="profimgcache_S"
90 # Start debug logging
91 logtag="${S4WORLD:+{$S4WORLD\}}"
92 exec 3>> $logdir/debug.out
93 err() {
94 echo "[`date +%F-%T%z`]$logtag $@" 1>&3
95 }
96 case "$HTTP_USER_AGENT" in
97 *i[Pp]hone*|*[Aa]ndroid*) touchpanel=1 ;;
98 *) touchpanel="" ;;
99 esac
101 # If S4MASTERDB is set, behave in another world
102 ### if [ -n "$S4MASTERDB" -a -s "$S4MASTERDB" ]; then
103 # If S4WORLDLIST is set, this s4 have world!
104 if [ -n "$S4WORLDLIST" ]; then
105 . ./s4-world.sh 2>> $logdir/debug.out
106 # Variables set in s4-world.sh
107 # $S4WORLDS, $S4WROLDNAME, $S4WORLDGRPS
108 # Files created in s4-world.sh
109 # $worldlistfile, $worldoptionfile, $worldnamefile, $worldgrpfile
110 fi
113 [ -f ./s4-cgi.sh ] && . ./s4-cgi.sh
115 : <<EOF
117 !! 検索等でblogテーブル参照時は sql4readableblogs() で定義される
118 !! readableblogs テーブルを使うこと
119 資料配布、グループ管理・ML、ファイル交換、クリッカー、アンケート
120 レポート提出管理
121 ひとつのarticleをheadingにして新規ツリーを作成、あるといいかも。
123 [2016]
124 7/12 根本への反省
125 * cgi自身の $1, $2 での切り替えでなく、CGI変数での受け渡しにすべき。
126 arg1/arg2/arg3 的に $1 に / 区切りでつけた方がよかったかな。
128 [以下2015]
129 8/4 ○グループに承認加入モードを追加
130 ○グループに参加していない場合は grpaction できない
131 Web
132 締切設定
134 8/2 ○s4.cgi生成系 → index.cgi生成
135 ○自分の提出物リスト
137 7/19 ○設置
138 ○一斉送信
139 ○getparfilename の tmpd の扱い
140 ○やっぱりs4にしようかな
141 7/18 ○書込著者からホームへのリンク
142 7/17 ○個人blogに「レポート提出用」がついたときの挙動
143 ○添付ファイル回収
144 ○imgcacheは別ディレクトリにしないと + .htaccess
145 7/15 ○レポート提出モードの表示を付ける
146 管理者権限での削除? → まだいいか
148 7/13 ○前回アクセス基準の新着数は欲しいなあ
149 ○レポート提出はどうしよう
150 → ○blogにモードを追加:
151 ○レポート提出モード
152 添付ファイル (誰が見たかログ)
153 クリッカーは別立てメニューにしないと(管理者がON/OFF)
154 ○添付ファイルの読み出し権(6/22から) ← モードで対処
157 7/9 ○管理者の追加
158 △グループメンバの操作 → 要不要を吟味
159 ○グループ情報編集の行先はそのグループがいい?
160 ○新規グループの作成はどこから入るか
161 △グループホームとユーザホームを揃える
163 7/8 ○グループ一覧をユーザ一覧と揃える。
165 7/6の次 ○グループのconf編集の入口
166 ○グループ検索
168 6/22の次 ○ホーム画面、○招待状、親記事追跡、○編集ボタン、削除ボタン、
171 6/7の次 ○blogを作ってみる || userconfig || _mのまとめ編集(削除)
172 6/7の次の次 ○userconfigの画面だけ作ってみる。
174 ○ 5/28の次 edittableに「削除」ボタンを足す
175 ○6/1 par2tableを triplex 対応に
176 select "yuuji@gentei.org",var,"text",NULL,val from par where var in (select col from _tblconf where tbl="/user" and keytype in ('p', 's'));
177 →とすると 一気に
179 ## form.def を考えなおそう:
180 ## userのように必須カラムを決まった位置に付ける?
181 ## 必須カラム、owner(foreign key passwd(name)), update datetime
182 ## ユーザ管理とグループ管理はデフォルトで持たせてしまえ
184 ## 縦持ちデータの入力/編集を供給する関数 single + multi
185 ## 持てるテーブル構造はシステム標準5種 + ユーザ定義2種類
186 ## 1. passwd
187 ## 2. grp
188 ## 3. grp_mem
189 ## 4. topic 記事のIDとなる
190 ## 5. topic_cont 特定IDの記事の内容物
191 ## 6. list 繰り返し登場あり
192 ## 7. hash 繰り返し登場なし
194 ## ● listの定義:
195 ## create table list(id unique, parentID, type, value);
196 ## ● hashの定義:
197 ## create table hash(parentID, type, value, primary key(parentID, type));
199 ## グループ属性: community, friend
200 ## ○ blob使えるのかな。streamで行けるのか? xxdで行けた。ありがたい。
201 ## form-defとtableは1対1対応でいいか
202 ## csv2sq3 で .csv.sq3 の Makefile
204 ## 書き込みオブジェクトとは何か?
205 ## topic : id, belongto, title, owner, mode
206 ## type := root | comment
207 ## topic_cont : id, topicid(F), ppath, contenttype, filename, content,
208 ## unique(id, filename)
209 ## type := body(single) | attachment(multi)
211 ## group := name(P), tag, gecos, owner(F), mode
212 ## tag := personal | friend | ... any string
213 ## group_member := gname(F), type, name(F), UNIQUE(gname, type, name)
214 ## type := "u" | "g"
215 ## できたー!
216 ## with recursive allmem as (select * from grp_mem where gname='bar' union all select grp_mem.* from grp_mem,allmem where allmem.name=grp_mem.gname) select * from allmem where type='u';
217
218 ↓以下に変更
219 with recursive allmem as
220 (select gname,val from grp_m where gname='foo'
221 union all select grp_m.gname,grp_m.val from
222 grp_m,allmem where allmem.val=grp_m.gname)
223 select val from allmem where val in (select name from user);
226 with recursive allmem as
227 (select gname,val from grp_m where gname='foo'
228 union all select grp_m.gname,grp_m.val from grp_m,allmem
229 where allmem.val=grp_m.gname)
230 select a.*, coalesce(b.val,a.val) from allmem a left join grp_mem_s b
231 on a.gname=b.gname and a.val=b.user and b.key='email'
232 where a.val in (select name from user);
235 ## triggerもできた。
236 ## 5/22から:グループ作成画面
237 ## 埋め込み画像 data:CONTENT-TYPE;base64,.....
239 ## 考え得るノードタイプ
240 ## 日報 - 個人所属かグループ所属か
241 ## 課題提出 - 個人所属かグループ所属か
242 ## グループ管理
243 ## 個人情報管理
244 ##
246 ## 例: group:sip - topic:1:sip:Aperture:yuuji:rw
247 ## - topic:2:sip:ISO:yuuji:rw
248 ## topic_cont 1:1:/:body:text...Aperture
249 ## 2:1:/1:body:text..Aperture
250 ## 3:1:/1:attachment:binary..Aperture
251 ## 4:1:/2:body:text..Aperture
252 ## 5:1:/2:attachment:binary..Aperture
253 ## 6:2:/:body:text..ISO
254 ## 7:2:/6:body:text..ISO
255 ## 8:2:/6:attachment:binary..
257 ## ログテーブル
258 ## time, who, action, tbl, id idなんか取れるかな
262 ■表設計
263 * 3つの表に分散管理
264 id格納表 + hash表 + list表
265 * *_s *_m
269 user, user_map, user_col
271 ■抽象エントリタイプ
272 * user
273 idとして機能 → table中の owner に自動挿入(?)
274 * group
275 権限判定に利用
276 * serial
277 自動idとして機能
278 * password
279 入力 type=passwordで入力
280 変更 oldpasswd, password×2 で確認後修正
281 * session
282 password認証後のセッションキーとして機能
283 * text
284 入力 type=text
285 * textarea
286 入力 textarea
287 * image|document
288 入力 type=fileで入力し、mime-typeを確認
289 * owner
290 入力時の $user で、外部キー制約が付く
291 * gowner
292 グループとしての所有者で、外部キー制約が付く
293 * timestamp
294 datetime()
295 * parent
296 木構造の場合の親の位置
297 * path
298 木構造の場合の自分の位置
300 格納タイプ
301 * list
302 表 parentID, key, val でUNIQUE(parentID, key, val)
303 * hash
304 表 parentID, key, val でUNIQUE(parentID, key)
306 オブジェクトタイプ
307 * entry
308 id, title, owner
309 * textpart
310 id, parentID, text
311 * binarypart
312 id, parentID, contenttype, filename, content
313 * content
314 hash(textpart), list(binarypart)
315 * topic
316 id, hash(content), list(reply)
317 * reply
318 id, parentID, content
319 * blog
320 list(entry)
321 blog = [topic, list(reply)]
324 blog = [ {"title" => "hoge", "owner" => "yuuji", "date" => "2015-04-27",
325 "text" => "hogehoge ..",
326 "reply" => [ {"serial" => 1,
327 "author" => "taro",
328 "date" => "2015-04-28",
329 "parent" => "/",
330 "path" => "/1",
331 "text" => "blah, blah, ....",
332 "image" => ["a.jpg", "b.jpg"] },
333 {"serial" => 2,
334 "author" => "hanako",
335 "date" => "2015-04-29",
336 "parent" => "/",
337 "path" => "/2",
338 "text" => "blah, blah, ....",
339 "image" => [] }]},
340 {"title" => "buha", ...} ]
343 user:=
344 ユーザ名(英数字):name:p:text:length="20" maxlength="40"
345 パスワード:pswd:s:password:length="20" maxlength="40"
346 説明(日本語OK):gecos:s:text:length="20" maxlength="40"
347 セッションキー:skey:s:session
348 メイルアドレス:email:m:text:length="20" maxlength="40"
349 住所:address:m:textarea:maxlength="400"
350 プロフィール画像:profimg:m:image:maxlength="400K"
351 履歴書:profpdf:m:document:maxlength="4M"
353 変換表
354 /user/email=m
356 blog:=
357 シリアル:id:p:serial
358 タイトル:title:s:text:
359 所有者:owner:s:owner:
360 時刻:ctime:s:stamp:
361 リード文:heading:s:textarea:
362 リプライ:reply:m:*article:
364 article:=
365 シリアル:id:p:serial
366 筆者:author:s:owner
367 時刻:ctime:s:stamp:
368 参照元:parent:s:parent:
369 パス:path:s:path:
370 本文:text:s:textarea:
371 画像:image:m:image:
373 履歴書:profpdf:m:document:maxlength="4M"
376 EOF
378 logstart() {
379 echo "`date '+%F %T'`:[${user:-NULL}]$logtag <<<" >> ${1:-$querylog}
380 }
381 logend() {
382 echo ">>>$logtag" >> ${1:-$querylog}
383 }
384 sqlog() {
385 logstart
386 if [ -z "$1" ]; then
387 cat >> $querylog
388 else
389 echo "$*" >> $querylog
390 fi
391 logend
392 }
393 sq() {
394 # ./args.rb -cmd ".timeout 3000" "$@"
395 logstart
396 if [ -z "$1" ]; then
397 tee -a $querylog|sqlite3 -cmd 'PRAGMA foreign_keys=ON' -cmd ".timeout 3000"
398 else
399 echo "$@" >> $querylog
400 sqlite3 -cmd 'PRAGMA foreign_keys=ON' -cmd ".timeout 3000" "$@"
401 ###sqlite3 -bail -cmd 'PRAGMA foreign_keys=ON' -cmd ".timeout 3000" "$@"
402 fi
403 logend
404 }
405 dbsetup() {
406 pipedir=$tmpdir/pipedir
407 [ -d $pipedir ] || mkdir -p -m 1777 $pipedir
408 [ -d $dbdir ] || mkdir -m 1775 $dbdir
409 suf=`date +%s`
410 sqi=$pipedir/sqi-$suf.$$
411 sqo=$pipedir/sqo-$suf.$$
412 mkfifo $sqi $sqo
413 #tail -f $sqi | sq $db & # "tail -f" is too heavy. DO NOT USE!!
414 sq $db < $sqi &
415 sq3pid="`jobs -p` $!"
416 if $isCGI; then
417 exec 2>> $logdir/error.out
418 fi
419 exec 5> $sqi # Turning $sqi access through fd5 for continuous open state
420 chmod o-r $logdir/error.out $logdir/debug.out
421 rm $sqi
422 # Attach supplemental DB
423 cat >&5 <<-EOF
424 .output /dev/null
425 ATTACH DATABASE "$sessdb" AS tmp;
426 CREATE TABLE IF NOT EXISTS $sesstb(user, skey, expire, UNIQUE(user, skey));
427 DELETE FROM $sesstb WHERE expire < datetime('now', 'localtime');
428 DELETE FROM $sesstb WHERE expire is NULL or expire = '';
429 .output stdout
430 EOF
431 }
432 cleanup2() { # Dirty workaround for produced zombie processes
433 if [ -n "$HTTP_USER_AGENT" ]; then # When called from httpd
434 pkill -9 -u `id -u` -P 1
435 fi
436 chmod o-r $querylog $db $sessdb # make sure
437 }
438 cleanup() {
439 trap '' INT HUP EXIT TERM PIPE
440 echo .quit >&5
441 kill $sq3pid
442 kill $sq3pid
443 rm -f $sqo $sqi
444 rm -rf $tmpfiles
445 cleanup2
446 }
447 # We want to use piped function to put querylog, but we use
448 # simple redirection for the sake of speed.
449 query() {
450 # echo ".once $sqo" >&5
451 echo ".output $sqo" >&5
452 logstart
453 if [ -z "$1" ]; then
454 tee -a $querylog
455 else
456 echo "$@" >> $querylog
457 echo "$@"
458 fi >&5
459 echo ".output stdout" >&5
460 cat $sqo
461 rc=$?
462 logend
463 return $rc
464 }
465 _m4() {
466 #S4NAME=f,f,f
467 m4 ${S4NAME:+"-D_S4NAME_=${S4NAME}"} ${S4CSS:+-D_S4CSS_="$S4CSS"} \
468 ${S4WORLD:+-D_S4WORLD_="$S4WORLD"} \
469 ${S4WORLDNAME:+-D_S4WORLDNAME_="$S4WORLDNAME"} \
470 ${S4WORLDGRPS:+-D_S4WORLDGRPS_="$S4WORLDGRPS"} \
471 ${S4WORLDS:+-D_S4WORLDS_="$S4WORLDS"} "$@"
472 }
473 ismember() {
474 # $1=user, $2=group
475 err ismem: "select user from grp_mem where gname=$(sqlquote $2) and user='$1';"
476 test -n "`query \"select user from grp_mem where gname=$(sqlquote \"$2\") and user='$1';\"`"
477 }
478 isuser() { # Check if $1 is a valid user
479 test -n "`query \"select name from user where name='$1';\"`"
480 }
481 isgroup() { # Check if $1 is a valid group
482 err isgroup: "select gname from grp where gname=$(sqlquote $1);"
483 test -n "`query \"select gname from grp where gname=$(sqlquote \"$1\");\"`"
484 }
485 isgrpowner() (
486 # $1=user, $2=group
487 gn=`sqlquote "$2"`
488 sql="select user from grp_adm where gname=$gn and user='$1';"
489 err isgrpowner: $sql
490 test -n "`query $sql`"
491 )
492 isgrpownerbygid() (
493 # $1=user, $2=group-rowid
494 sql="select user from grp_adm where gname=(select gname from grp where rowid=$2) and user='$1';"
495 err isgrpownerbygid: $sql
496 test -n "`query $sql`"
497 )
498 getgroupadminmails() {
499 # $1=group
500 for i in $(getgroupadmins "$1"); do
501 email4group "$1" "$i" ;
502 done
503 }
504 getgroupadmins() { # $1=group
505 # This function is called in a backquote, so needn't to be subshellized
506 qgrp=`sqlquote "$1"`
507 query "select user from grp_adm where gname=$qgrp;"
508 }
509 getgroupattr() { # $1=group $2=attr
510 # This function is called in a backquote, so needn't to be subshellized
511 getvalbyid grp $2 \
512 $(query "select rowid from grp where gname=`sqlquote \"$1\"`;")
513 }
514 getgroupbyid() {
515 # $1=id|gname
516 sql="select coalesce((select gname from grp where gname=$(sqlquote \"$1\")),
517 (select gname from grp where rowid=$(sqlquote $1)));"
518 # err ggbyid: `echo $sql`
519 query $sql
520 }
521 isfilereadable() { # $1=user $2=tbl $3=rowid
522 # Return true if user($1) can read attachment files in tbl($2):rowid($3)
523 [ -z "$1" -o -z "$2" -o -z "$3" ] && return 1 # invalid argument
525 # Return true when anonymous mode
526 [ "$anonymousmode" ] && return 0
527 # case `getvalbyid blog mode $2` in
528 # normal|*open*|"") return 0 ;;
529 # *closed*)
530 # owner=`getvalbyid blog owner $2`
531 # if isgrp $owner; then
532 # isgrpowner $1 $owner && return 0 || return 1
533 # elif isuser $owner; then
534 # [ x"$1" = x"$owner" ] && return 0 || return 1
535 # fi
536 # esac
537 # ↑ 要はこういう処理を↓で一気にやっている
538 sql="with getblog as (
539 select key,val from blog_s where id=(
540 select blogid from article where id in
541 (select id from $2 where rowid=$3))),
542 getowner as (select val from getblog where key='owner'),
543 getauthor as (select author from article where id=(select id from $2 where rowid=$3)),
544 isgrp as (SELECT val from getowner WHERE val IN (select gname from grp)),
545 isgrpadm as (select user from grp_adm where
546 gname=(select val from getowner) and
547 user='$1'),
548 getmode as (select val from getblog where key='mode')
549 select case
550 when (select author from article where
551 id=(select id from $2 where rowid=$3))='$1'
552 then 'author'
553 when (select val from getmode) in ('report-open', 'normal')
554 then 'open'
555 when (select val from getmode) in ('quiz', 'enquete')
556 then CASE
557 WHEN (SELECT val FROM isgrp) IS NULL
558 THEN
559 CASE WHEN (SELECT val from getowner)
560 IN ('$user', (SELECT author FROM getauthor))
561 THEN 'owner-or-user-article-is-readable'
562 ELSE ''
563 END
564 WHEN (select user from isgrpadm) IS NOT NULL
565 THEN 'i-am-admin'
566 ELSE (SELECT author from getauthor WHERE author IN (SELECT user FROM grp_adm WHERE gname=(SELECT val FROM getowner)))
567 END
568 when (select val from getmode) is null
569 then 'open'
570 when (select val from getowner) in (select gname from grp)
571 then (SELECT user FROM isgrpadm)
572 when (select author from article where
573 id=(select id from $2 where rowid=$3))='$1'
574 then 'user+author'
575 else '' end;"
576 ## err isfilereadable: sql="`echo $sql`"
577 # caseのネストで内側のcaseがスカラーtrueを返しても外側はtrue扱いにならない
578 # result=`query "$sql"`
579 # err FileAccessibility=$result
580 [ -n "`query $sql`" ] || return 2
581 }
582 linkhome() {
583 # $1=UserOrGroupRowid
584 echo -n "<a href=\"$myname?"
585 if isuser $1; then
586 err "select 'home+'||rowid from user where name='$1';"
587 query "select 'home+'||rowid from user where name='$1';"
588 name=`gecos $1|htmlescape`
589 else
590 _grid=`numericalize "$1"`
591 echo -n "grp+$1"
592 name=`query "SELECT gname FROM grp WHERE rowid=$_grid;"|htmlescape`
593 fi
594 echo "\">$name</a>"
595 }
596 hreflink() {
597 # s4 specific notation:
598 # ^href=URL
599 # ^iframe=URL
600 # ^video=URL
601 # [[#NUM]] - Jump to article ID NUM
602 # [[#Keyword] - Jump to keywrod search for "Keyword"
603 # OSM umap Wikistyle Notation:
604 # [[URL]] - Simle Link
605 # [[URL|Word]] - Link with anchor word
606 # {{URL}} - <img src="URL">
607 # {{URL|width}} - <img src="URL" width="width">
608 # {{{URL}} } - <iframe src="URL"></iframe>
609 # {{{URL|height}} - <iframe src="URL" height="height"></iframe>
610 # Other Style
611 # ---- - <hr> (In the beginning of line)
612 # *Word* - <em>Word</em>
613 # _Word_ - <em>Word</em>
614 # **Word** - <strong>Word</strong>
615 # __Word__ - <strong>Word</strong>
616 # SPC+SPC+$ - <br>
617 cb='<input type="checkbox" class="s4-checkbox" disabled'
618 checkboxON="${cb} checked>"
619 checkboxOFF="${cb}>"
620 _hrefptn="[-A-Za-z0-9,.:;/~_%#&+?=@!]*"
621 _hrefptn="[A-Za-z0-9/~%+?=@!.][^][()<> ]*" # URL should start with ASCII
622 sed -e "s|\[\[\#\([0-9][0-9]*\)\]\]|<a href=\"?aid\1\">#\1</a>|g" \
623 -e "s|\[\[#\([^]&]*\)\]\]|<a href=\"?kwd=\1\&stage=searchart\">\#\1</a>|g" \
624 -e "s|\[\[\($_hrefptn\)\|\([^]]*\)\]\]|<a href=\"\1\">\2</a>|g" \
625 -e "s|\[\[\($_hrefptn\)\]\]|<a href=\"\1\">\1</a>|" \
626 -e "s|{{{\($_hrefptn\)\|\(.*\)}}}|<iframe src=\"\1\" height=\"\2\"></iframe>|g" \
627 -e "s|{{{\($_hrefptn\)}}}|<iframe src=\"\1\"></iframe>|g" \
628 -e "s|{{\($_hrefptn\)\|\(.*\)}}|<img src=\"\1\" width=\"\2\">|g" \
629 -e "s|{{\($_hrefptn\)}}|<img src=\"\1\">|g"\
630 -e "s|^href=\($_hrefptn\)|<a &>\1</a>|" \
631 -e "s|^iframe=\($_hrefptn\)|<iframe src=\"\1\"></iframe>|" \
632 -e "s|^video=\($_hrefptn\)|<video controls><source height=\"320\" src=\"\1\"></video>|" \
633 -e "s,^#### *\(.*\),<h4>\1</h4>," \
634 -e "s,^### *\(.*\),<h3>\1</h3>," \
635 -e "s,^## *\(.*\),<h2>\1</h2>," \
636 -e 's,^----*$,<hr>,' \
637 -e 's, \*\*\([^* |][^*|]*[^ |]\)\*\* ,<strong>\1</strong>,g' \
638 -e 's, __\([^_ |][^_]*[^ ]\)__ ,<strong>\1</strong>,g' \
639 -e 's, \*\([^* |][^*|]*[^ |]\)\* ,<em>\1</em>,g' \
640 -e 's, _\([^_ ][^_]*[^ ]\)_ ,<em>\1</em>,g' \
641 -e 's, $,<br>,' \
642 -e "s,- \[ *\]\([^|-]*\),${checkboxOFF}<label>\\1</label>,g" \
643 -e "s,- \[[^ ]\]\([^|-]*\),${checkboxON}<label>\\1</label>,g" \
645 }
646 minitbl() {
647 sed -n '
648 /^|.*|/ {; # If the line begin with "|" and has 2 or more "|"
649 s,|$,,; # Remove trailing "|" first
650 s,|\* *\([^|]*\) *,<th>\1</th>,g; # "|*..." to "<th>...</th>"
651 s,| *\([^|]*\) *,<td>\1</td>,g; # "|..." to "<td>...</td>"
652 s,^,<tr>,; s,$,</tr>,; # Enclose with "<tr>" and "</tr>"
653 H; # Concat this line to HoldSpace
654 s/.*//; # Delete PatternSpace for finalization
655 $ b cont
656 d; # If in final line, output the rest, else jump to next turn
657 }
658 :cont
659 x; # For non-"|" lines, check HoldSpace
660 /^./ {; # If HoldSpace has "|" table elements
661 s|^.|<table class="mini">|; # Enclose whole elements like this:
662 # . of ^. is workaround for FreeBSD sed
663 # s|$|</table>|; # <table class="mini">..\n..</table>
664 p; # Print whole "table" element
665 s/.*//; # Erase all when done.
666 x; s|^|</table>|; x; # Preppend /table to the next line
667 }
668 x; # Back to the newest line
669 p; # Print rest' | miniul
670 }
671 miniul() {
672 sed -e '
673 /^\* / {; # 行頭 "* "
674 x; s,^,<ul>,; x; # 1週目: ホールドスペース先頭に <ul> を
675 :top
676 s/\n//;
677 s/^ *//; # 2周目以降: 行頭空白削除
678 s,\* ,,; # まず行頭の "* " を消しておく
679 H; # 置き換え結果をホールドスペースに追加
680 s/.*//; # パターンスペースは消しておく
681 # ↓最終行なら残ったホールドスペース処理のため :cont へ
682 $ b cont
683 N; # 次の行を読む
684 s/\n//; # 空白始まりは継続行
685 /^ /b top
686 x; s/\n/<li>/; s,$,</li>,; # 継続行でなければ <li></li> で囲む
687 p; s/.*//;
688 x; # 次も "* " ならループを抜けない
689 /^\* /b top
690 s,^,</ul>,; # 次が一般行なら箇条書終わり
691 }
693 :cont
694 x; # 行頭| 以外の行:
695 /./ {; # ホールドスペースに文字列があれば
696 s/^\n/<li>/; s,$,</li></ul>,; # 箇条書を書き切って終わり
697 H; x
698 }
699 x'
700 }
701 acclog() (
702 # $1=table, $2=rowid
703 n=${2%%[!-0-9]*} # Remove non-digit chars from $2(should be rowid)
704 if [ -n "$n" ]; then
705 now=`date +"%F %T"`
706 #query "replace into acclog values('$user', '$1', '$n', '$now');"
707 #query "replace into acclog values('$user', '$1', $n, '$now');"
708 query "replace into tblaccesses values('$user', '$1', $n, '$now');"
709 fi
710 )
711 gecos() (
712 u=`sqlquote "${1:-$user}"`
713 query "select gecos from gecoses where name=$u;"
714 )
715 setpar() {
716 # 2020/5/14 Add dirty code to cache essential params
717 if [ x"$session" = x"$main_session" ]; then
718 case "$1" in
719 user) _user="$v" ;;
720 skey) _skey="$v" ;;
721 esac
722 fi
723 query "replace into par values('$session', '$1', '$2', \"$3\");"
724 }
725 unsetpar() {
726 for i; do
727 if [ x"$session" = x"$main_session" ]; then
728 case "$i" in
729 user|skey) unset _$i ;;
730 esac
731 fi
732 query "DELETE FROM par WHERE var='$i' AND sessid='$session';"
733 done
734 }
735 replpar() {
736 query "update par set val=\"$3\" where sessid='$session' and var='$1' and type='$2';"
737 }
738 getpar() {
739 # err GETPAR=$1, _user=$_user
740 val=""
741 if [ x"$session" = x"$main_session" ]; then
742 case "$1" in # Dirty cache mechanism for high-load average
743 user) val=$_user ;;
744 skey) val=$_skey ;;
745 esac
746 fi
747 val=${val:-`query "select val from par where var='$1' and sessid='$session' $2;"`}
748 ## err getpar/val1: "val=[$val]"
749 if [ -z "$val" ]; then
750 val=`query "select val from cookie where var='$1' and sessid='$session' $2;"`
751 fi
752 ## err getpar/val2: "val=[$val]"
753 case "$var" in
754 owner)
755 if [ x"$user" = x"$val" ]; then
756 echo $user; return
757 elif ismember $user $val; then
758 echo $val; return
759 fi ;;
760 esac
761 ## err getpar/ret: "val=[$val]"
762 echo "$val"
763 }
764 setskey() {
765 # For quick response...(?)
766 query "REPLACE INTO $sesstb VALUES('$1', '$2', datetime('now', 'localtime', '$timeout'));"
767 }
768 chkskey() {
769 # $1=sesskey, $user=LoginUserName
770 test -z "$1" && return 1
771 repl=`query "SELECT rowid,user FROM $sesstb WHERE user='$user' AND skey = '$1';"` || return 2
772 rowid=${repl%%\|*}; repuser=${repl#*\|}
773 if [ -n "$rowid" -a x"$user" = x"$repuser" ]; then
774 query "UPDATE $sesstb SET expire=datetime('now', 'localtime', '$timeout') WHERE rowid=$rowid;" # Errors can be ignored
775 return 0
776 fi
777 return 1
778 }
779 resetskey() {
780 if [ -n "$_user" ]; then
781 query "DELETE FROM $sesstb WHERE user='$_user';"
782 fi
783 }
784 getpartype() {
785 query "select type from par where var='$1' and sessid='$session' $2;"
786 }
787 getparcount() {
788 query "select count(*) from par where var='$1' and sessid='$session' $2;"
789 }
790 getparfilename() {
791 # null if type of $1 is not file
792 (f=`query "select val from par where var='$1' and sessid='$session' and type='file' $2;"`
793 [ -n "$f" ] && echo $f)
794 }
795 sqlquote() {
796 (v="$1"
797 case "$v" in
798 "") return ;; # null
799 "X'"*) # quoted hex string
800 echo $1 ;;
801 *\"*) # string including dbl-quote"
802 v=`echo "$v"|sed -e 's/\"/\"\"/g'`
803 echo "\"$v\""
804 return ;;
805 *.*.*|*-*-*|*[Ee]*[Ee]*|[Ee]*|*[\ -,:-df-~]*) # string
806 echo "\"$v\""
807 return ;;
808 *)
809 if expr "$v" : '[-0-9.Ee][-0-9.Ee]*$' >/dev/null 2>&1; then
810 echo $v # MAYBE numeric, maybe...
811 else
812 echo "\"$v\""
813 fi ;;
814 esac)
815 }
816 sqlquotestr() (
817 case "$1" in
818 *\'*) v=`echo "$1"| sed "s/'/''/g"`
819 echo "'$v'" ;;
820 *) echo "'$1'" ;;
821 esac
822 )
823 mktempd() {
824 TMPDIR=$tmpd mktemp -d -t $session
825 }
826 getcachedir() { # $1=maintable
827 if [ -n "$imgcached" ]; then
828 echo $imgcached/$(echo ${1:-hoge}|md5)/$thumbxy
829 else
830 echo $tmpd/$thumbxy
831 fi
832 }
833 getval() {
834 # $1=table $2=col $3(optional)=condition
835 case `gettbl_coltype "/$1/$2"` in
836 user|author) # author added 2015-06-18 for article(author)
837 echo "$user" ;;
838 stamp|datetime)
839 date "+%F %T" ;;
840 serial)
841 (s=`getpar $2`
842 if [ -n "$s" ]; then echo $s; else echo "`date +%s`x$$"; fi) ;;
843 *)
844 getpar "$2" "$3";;
845 esac
846 }
848 getvalquote() {
849 # $1=table $2=col $3(optional)=condition
850 (v=`getval "$@"`
851 case "$v" in
852 "") echo NULL ;;
853 *) sqlquote "$v" ;;
854 esac)
855 }
856 getparquote() {
857 sqlquote "`getpar $1`"
858 }
859 getbinbyid() {
860 # $1=tbl $2=col $3=rowid $4=tmpdirForBinary
862 }
863 getvalbyid() {
864 # $1=tbl $2=col $3=rowid $4=tmpdirForBinary
865 # If two or more values found, save them to $tmpd/${column}.$N and
866 # store the number of files into $tmpd/${column}.count and
867 # their each rowid stored into $tmpd/${column}.$N.rowid.
868 ## err gtb-$1=`gettblcols $1`, tbl=$1, col=$2, '$3'=$3
870 (for c in `gettblcols $1`; do
871 if [ x"$2" = x"$c" ]; then
872 ###sq $db "select $2 from $1 where rowid=$3"
873 query "select $2 from $1 where rowid=$3;"
874 return
875 fi
876 done
877 rowid=$3
878 pk=`gettblpkey $1`
879 key=`query "select $pk from $1 where rowid=$3;"`
880 getkey="(select $pk from $1 where rowid=$3)"
881 td=${4:-$tmpd}
882 [ -d $td ] || mkdir -p $td
883 ### err "select $pk from $1 where rowid=$3" - key=$key '$4(tmp)'=$4
884 for kt in s m; do
885 t=${1}_$kt
886 for c in `gettbl_${kt}_cols $1`; do
887 vcount=1 # count(val)
888 if [ x"$2" = x"$c" ]; then
889 #### cond="$t where $pk=\"$key\" and key=\"$c\"" #2015-07-22
890 cond="$t where $pk=$getkey and key=\"$c\""
891 val=`query "select val from $cond limit 1;"`
892 type=`query "select type from $cond limit 1;"`
893 if [ $kt = m ]; then
894 ###vcount=`sq $db "select count(val) from $cond"`
895 # Reset val to store filenames if type is string
896 val=`query "select val from $cond and type like 'file:%' order by rowid;"`
897 ## err gvb1-sql: "select count(val) from $cond;"
898 vcount=`query "select count(val) from $cond;"`
899 echo $vcount > $td/$c.count
900 i=0
901 ## err gvbid: i=$i vcount=$vcount
902 while [ $i -lt $vcount ]; do
903 slice="order by rowid limit 1 offset $i"
904 i=$((i+1))
905 fn=$c.$i
906 ## err td=$td, fn=$fn, type=$type, val="[$val]"
907 case $type in
908 file:*)
909 #file=$td/$val
910 r_f=`query "select rowid||'//'||val from $cond $slice;"`
911 f_rid=${r_f%%//*}
912 file=$td/${r_f##*//}
913 # FOR SPEED: Skip file generation if imgcache exists
914 [ -s "$file" -a -s "$td/$fn.rowid" -a -s "$file.rowid" ] \
915 && [ x"$f_rid" = x"`cat $td/$fn.rowid`" ] \
916 && continue
917 # err gvbid-get="select quote(bin) from $cond $slice;"
918 ## err output: "fn=[$fn] file=[$file]"
919 sq $db<<EOF | unhexize > "$file"
920 .output '$td/$fn.rowid'
921 select rowid from $cond $slice;
922 .output '$td/$fn'
923 select val from $cond $slice;
924 .output '$td/${fn}.content-type'
925 select substr(type, 6) from $cond $slice;
926 .output stdout
927 select quote(bin) from $cond $slice;
928 EOF
929 ## err gvbid-get2: "`ls -lF $file`"
930 ## err i=$i - file=$file rowid=`cat $td/$fn.rowid`
931 cp "$td/$fn.rowid" "$file.rowid" 2>&3 # for convenience
932 cp "$file" "$file.orig" 2>&3
933 ls -lh "$file" |
934 awk '{print $5"B"}'|sed 's/BB/B/' > "$file.size"
935 case "$type" in
936 *:[Ii]mage*) mogrify -geometry $thumbxy "$file" ;;
937 ### ここのアイコンを増やしたい
938 *|*:[Aa]pplication*)
939 convert -geometry $thumbxy $imgdir/file-icon.png \
940 png:- > "$file"
941 ;;
942 esac
943 ;;
944 *)
945 sq $db<<EOF
946 .output $td/$fn.rowid
947 select rowid from $cond $slice;
948 .output $td/$fn
949 select val from $cond $slice;
950 EOF
951 val=$val${val:+$nl}"`echo $fn`" # should be delimited by newline
952 ;;
953 esac
954 done
955 else
956 rm -f $td/$c.count
957 case $type in
958 file:*)
959 echo "$val" \
960 | while read fn; do
961 file=$td/$fn
962 if [ ! -s "$file" ]; then
963 ## sq $db "select quote(bin) from $cond and val=\"$fn\"" \
964 query "select quote(bin) from $cond and val=\"$fn\";" \
965 | unhexize > "$file"
966 ##@@## -- echo ${type#file:} > "$file.content-type"
967 case $type in
968 *:[Ii]mage*) mogrify -geometry $thumbxy "$file" ;;
969 *:[Aa]pplication*)
970 convert -geometry $thumbxy $imgdir/file-icon.png \
971 png:- > $file ;;
972 esac
973 fi
974 done
975 ;;
976 esac
977 fi
978 echo "$val" # Keep newlines by ""
979 return
980 fi
981 done
982 done)
983 }
984 getvalbypkey() (
985 # $1=tbl $2=col $3=pkey $4=tmpdirForBinary
986 pk=`gettblpkey $1`
987 rowid=`query "select rowid from $1 where $pk='$3';"`
988 getvalbyid "$1" "$2" $rowid $4
989 )
990 getvalbycond() {
991 # $1=tbl $2=col $3=SQL-Condition
992 ###rowid=`sq $db "select rowid from $1 where $3"`
993 rowid=`query "select rowid from $1 where $3;"`
994 if [ -n "$rowid" ]; then
995 getvalbyid "$1" "$2" $rowid "$4"
996 fi
997 }
998 getpwfield() {
999 # getpwfield user column
1000 # val=`sqlite3 $db "select $2 from passwd where name='$1' $3"`
1001 val=`getvalbycond user $2 "name='$1'"`
1002 if [ -n "$val" ]; then
1003 echo "$val"
1004 return 0
1005 else
1006 return 1
1007 fi
1009 numericalize() {
1010 echo "${1%%[!0-9]*}"
1012 encode() {
1013 if [ -z "$sha1" ]; then
1014 if type sha1 >/dev/null 2>&1; then
1015 sha1=sha1
1016 elif type sha1sum >/dev/null 2>&1; then
1017 sha1=sha1sum
1018 elif type gsha1sum >/dev/null 2>&1; then
1019 sha1=gsha1sum
1020 fi
1021 fi
1022 $sha1 "$@" | cut -d' ' -f1
1024 enjpeg() {
1025 if [ -z "$cjpeg" ]; then
1026 if type cjpeg >/dev/null 2>&1; then
1027 cjpeg="cjpeg"
1028 else
1029 cjpeg="convert - jpeg:-"
1030 fi
1031 fi
1032 $cjpeg "$@"
1034 mycrypt() (
1035 key=$1 salt=$2
1036 # err \$2=$2
1037 case $2 in
1038 '$'*'$'*) salt=${salt#\$4\$}
1039 salt=${salt%\$*} ;;
1040 esac
1041 echo -n '$4$'"$salt"'$'
1042 echo "$salt$key" | encode || exit 1 # Abort if fail to call encode
1044 hexize() {
1045 if [ -z "$hexize" ]; then
1046 if type xxd >/dev/null 2>&1; then
1047 hexize="xxd -p"
1048 else
1049 hexize_hd() {
1050 hexdump -ve '1/1 "%.2x"'
1052 hexize="hexize_hd"
1053 fi
1054 fi
1055 cat "$@" | $hexize | tr -d '\n'
1057 unhexize() {
1058 if [ -z "$unhex" ]; then
1059 if type xxd >/dev/null 2>&1; then
1060 unhex="xxd -p -r"
1061 elif type perl >/dev/null 2>&1; then
1062 cat >$tmpd/unhex.pl<<EOF
1063 s/([0-9a-f]{2})/print chr hex \$1/gie
1064 EOF
1065 # Perl refuses -e in setuid circumstances, which can be absurdly
1066 # avoided by creating scripts in a file where its parent directory is
1067 # world writable...:)
1068 unhex="perl -n $tmpd/unhex.pl"
1069 fi
1070 fi
1071 cat "$@" | $unhex
1072 # cat $1 | tee /tmp/uh.in| $unhex | tee /tmp/uh.out
1074 percenthex() {
1075 hexize "$@" | sed 's/\(..\)/%\1/g'
1077 htmlescape() {
1078 sed -e 's/\&/\&amp;/g' -e 's/"/\&quot;/g' -e "s/'/\&apos;/g" \
1079 -e "s/</\&lt;/g; s/>/\&gt;/g" -e 's/`/\&#096;/g' -e 's/(/\&#040;/g' \
1080 -e 's/`/\&#96/'
1082 enascii() {
1083 if [ -z "$enascii" ]; then
1084 if type kakasi >/dev/null 2>&1; then
1085 enascii="kakasi -Ha -Ka -Ja -Ea -ka"
1086 else
1087 enascii_now=`date +%FT%T`
1088 enascii_sed() {
1089 nkf -Z0Z1Z2 \
1090 | sed -e "s/^/$enascii_now/" -e "s|[^-0-9.A-z/,()_=]|x|g"
1092 enascii="enascii_sed"
1093 fi
1094 fi
1095 cat "$@" | $enascii
1097 size_h() {
1098 i="$1" oi=$1
1099 set -- B B KB MB GB TB
1100 while [ $((i)) -gt 9 -a -n "$1" ]; do # -gt 9 means $oi > 1024
1101 oi=$i
1102 i=$((i/1024))
1103 shift
1104 done
1105 echo ${oi}$1
1107 gettblconf() {
1108 if [ -z "$tconfs" ]; then
1109 ## tconfs=`sq $db \
1110 tconfs=`query \
1111 "select tbl||'/'||col||'='||keytype||'/'||objtype from $conftbl;"`
1112 fi
1113 # /tb1/col1=p/text /tb1/col2=s/text /tb1/col3=m/image /tb2/col1=p/text ...
1115 gettblkeys() {
1116 # $1=tbl
1117 gettblconf
1118 echo "$tconfs" | fgrep "/$1/" | \
1119 (type="" keys="" fks="" cols="" scols="" mcols="" hcols=""
1120 while IFS='=' read tc conf; do # tc=/tb1/col1 conf=s/text
1121 col=${tc##*/} type=${conf%%/*}
1122 case $type in
1123 *p*)
1124 cols=$cols"${cols:+:}$col"
1125 keys=$keys"${keys:+:}$col" ;;
1126 *f*) cols=$cols"${cols:+:}$col"
1127 fks=$fks"${fks:+:}$col" ;;
1128 *m*) mcols=$mcols"${mcols:+:}$col" ;;
1129 *s*) scols=$scols"${scols:+:}$col" ;;
1130 esac
1131 case $type in
1132 *h*) hcols=$hcols"${hcols:+:}$col" ;;
1133 esac
1134 done
1135 echo "_keys=$keys _fks=$fks _cols=$cols _scols=$scols _mcols=$mcols _hcols=$hcols")
1137 gettblpkey() {
1138 # $1=tbl
1139 gettblkeys $1 | cut -d ' ' -f 1 | sed -e 's/.*=//' -e 's/:/ /g'
1141 gettblfkey() {
1142 (x=`gettblkeys $1`
1143 x=${x#*_fks=} # cut before "_fks=" including
1144 echo ${x%% *} | tr ':' ' ')
1146 gettblcols() {
1147 (x=`gettblkeys $1`
1148 x=${x#*_cols=} # cut before "_cols=" including
1149 echo ${x%% *} | tr ':' ' ')
1151 gettbl_s_cols() {
1152 (x=`gettblkeys $1`
1153 x=${x#*_scols=} # cut before "_scols=" including
1154 echo ${x%% *} | tr ':' ' ')
1156 gettbl_m_cols() {
1157 (x=`gettblkeys $1`
1158 x=${x#*_mcols=} # cut before "_mcols=" including
1159 echo ${x%% *} | tr ':' ' ')
1161 gettbl_h_cols() {
1162 (x=`gettblkeys $1`
1163 x=${x#*_hcols=} # cut before "_hcols=" including
1164 echo ${x%% *} | tr ':' ' ')
1166 gettbl_coltype() (
1167 gettblconf
1168 x=`echo "$tconfs"|fgrep $1=`
1169 x=${x#*=} # cut before =
1170 echo ${x#*/} # cut before p/ including
1172 is_hidden() {
1173 # $1=Tbl $2=col
1174 gettblconf
1175 x=`echo "$tconfs"|fgrep /$1/$2=`
1176 x=${x#*=} # cut before =
1177 x=${x%%/*} # cut after /
1178 case $x in
1179 *h*) return 0 ;;
1180 *) return 1 ;;
1181 esac
1184 dbsetbyid() {
1185 # $1=tbl $2=id $3=col $4=val/filename - &optional - $5=content-type
1186 (t0=$1 t=$1 p=$2 c=$3
1187 tsc=$t/$c val=$4
1188 quotedp=$(sqlquotestr "$p")
1189 unset primary update
1190 gettblconf
1191 #err tsc=$tsc, tconfs="$tconfs"
1192 conf=`echo "$tconfs"|fgrep "$tsc"=`
1193 #err conf=$conf
1194 case ${conf#*=} in
1195 p*) primary=1 ;;
1196 f*) update=1 ;;
1197 u*) ;;
1198 m*) t=${t}_m;;
1199 s*) t=${t}_s;;
1200 esac
1201 #err t=$t
1202 type=string fn=""
1203 case $conf in
1204 */password)
1205 type=encoded ### val=`echo $val|encode`
1206 ;;
1207 */image*|*/document*)
1208 type=`file --mime-type - < "$val" | cut -d' ' -f2`
1209 bin="X'`hexize "$val"`'"
1210 ;;
1211 esac
1212 pkey=`echo "$tconfs"|grep "${t0}/.*=p"|sed 1q`
1213 pkey=${pkey#/*/} # cut $tbl/
1214 pkey=${pkey%=p/*} # cut =p/... -> primary key
1215 if [ "$primary" ]; then
1216 nulls=`echo "$tconfs"|grep "$t/.*=[fu]/"|sed 's/^.*/, NULL/'|tr -d '\n'`
1217 ###sq $db "replace into $t values(\"$val\"$nulls)"
1218 query "replace into $t values(\"$val\"$nulls);"
1219 elif [ "$update" ]; then
1220 query "update $1 set $c=\"$val\" where $pkey=$quotedp;"
1221 else
1222 query "replace into $t values($quotedp, \"$c\", \"$type\", \"$val\", \"$bin\");"
1223 fi
1226 expire() (
1227 at="${1:-$timeout}"
1228 FMT="${2:-%F %T}"
1229 TZ=GMT gdate -d "$at" +"$FMT"
1231 addsession() {
1232 # expireをセット
1233 # loginの先にどの画面に行くかの状態遷移表書式を決める
1234 expire=`expire ${2:-"+1min"}`
1235 query "replace into session values('$1', '$expire');"
1236 # Remove old session parameters
1237 now=`expire now`
1238 query "delete from session where expire < '$now';"
1240 gencookie() (
1241 path=${URL#*:/}
1242 path=${URL%/*}
1243 expire="`expire '' '%a, %d-%b-%Y %H:%M:%S GMT'`"
1244 for kv; do
1245 # echo "Set-Cookie: $kv; expires=$expire; Path=$path"
1246 echo "Set-Cookie: $kv; expires=$expire;"
1247 done
1249 contenttype() {
1250 echo "Content-type: ${1:-text/html; charset=utf-8}"
1251 contenttype() {} # Only need to work once
1253 putheader() {
1256 putfooter() {
1257 _m4 -D_TITLE_="${TITLE:-$myname}" $layout/footer.m4.html
1259 getcookie() {
1260 for kv in `echo $HTTP_COOKIE|sed 's/[;, ]/ /g'`; do
1261 k="${kv%%=*}"
1262 v="`echo ${kv#*=}|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
1263 ## err "GetCookie: $k=[$v]"
1264 case "$k" in
1265 user) _user="$v" ;;
1266 skey) _skey="$v" ;;
1267 esac
1268 query "replace into cookie values('$session', '$k', 'string', \"$v\");"
1269 done
1271 genrandom() {
1272 # $1=columns (default: 10)
1273 dd if=/dev/urandom count=1 2>/dev/null|nkf -MB \
1274 | tr -d '+=\n'|fold -w${1:-10}|sed -n 10p
1276 genserial() {
1277 echo $((($(date +%s)-1433084400)/10))c$$
1279 smail() {
1280 # smail rcpts subj (file)
1281 # $SMAIL_TO <- Recipient value of To: header
1282 # $MAIL_FROM <- From: header value
1283 from=`echo "${MAIL_FROM:-$admin}"|nkf -jM|tr : /|tr -d '\n'`
1284 rcpt=`echo $1|tr ' ' '\n'|sort -u|tr '\n' ' '` # uniq and strip newlines
1285 rcptheader=`echo $1|tr ' ' '\n'|sort -u|sed '2,$s/^/To: /g'`
1286 subj=`echo $2|nkf -jM|tr -d '\n'`
1287 sender=${SENDER:-$admin}
1288 # Do not call m4 with directly passing text
1289 _r=$tmpd/rcpt
1290 echo -n "${SMAIL_TO:-$rcptheader}" > $_r
1291 replyto=${REPLYTO:+"Reply-to: $REPLYTO$LF"}
1292 (_m4 -D_RCPT_="spaste(\`$_r')" -D_REPLYTO_="$replyto" -D_SUBJ_="\`$subj'" -D_FROM_="$from" $msgdir/mail-header.m4
1293 cat $3 | nkf -jd ) | sendmail -f $sender $rcpt
1295 smail_queue_flush() {
1296 # $1=timelimit
1297 timelimit=`query "SELECT datetime('now', 'localtime', '-6 hours');"`
1298 rowids=$(sq $workdb <<-EOF
1299 SELECT rowid FROM smailq
1300 GROUP BY rcpts, subj
1301 HAVING min(time) < '$timelimit';
1302 EOF
1304 for rid in $rowids; do
1305 sq $workdb \
1306 "SELECT hex(rcpts),hex(subj) FROM smailq WHERE rowid in ($rid)" \
1307 | if IFS='|' read hexr hexs; then
1308 # err "hexrcpt=[$hexr] hexsubj=[$hexs]"
1309 rcpt=`echo "$hexr"|unhexize`
1310 subj=`echo "$hexs"|unhexize`
1312 # err ROWID==$rowid "sql=<<SELECT hex(rcpts),hex(subj) FROM smailq WHERE rowid=$rowid;>>"
1313 # err "rcpt=[$rcpt] subj=[$subj]"
1314 if sq $workdb <<-EOF | smail "$rcpt" "$subj"
1315 .separator "\n" "------------------\n\n"
1316 SELECT time, text FROM smailq
1317 WHERE rcpts=(SELECT rcpts FROM smailq WHERE rowid=$rid)
1318 AND subj=(SELECT subj FROM smailq WHERE rowid=$rid)
1319 ORDER by time;
1320 EOF
1321 then
1322 cat <<-EOF | sq $workdb
1323 DELETE FROM smailq
1324 WHERE rcpts=(SELECT rcpts FROM smailq WHERE rowid=$rid)
1325 AND subj=(SELECT subj FROM smailq WHERE rowid=$rid);
1326 EOF
1327 fi
1328 fi
1329 done
1331 smail_queue() {
1332 # $1=Rcpts, $@=subj
1333 now=`query "SELECT datetime('now', 'localtime');"`
1334 if [ $? -eq 0 ]; then
1335 rcpts="X'"`echo "$1"|hexize`"'"
1336 subj="X'"`echo "$2"|hexize`"'"
1337 text="X'"`cat | hexize`"'"
1338 err "smail_queue: rcpts=[$1] s=[$2] t:hex=[$text]"
1339 mintime=$(cat <<-EOF | sq $workdb
1340 CREATE TABLE IF NOT EXISTS smailq(rcpts, subj, text, time);
1341 INSERT INTO smailq VALUES($rcpts, $subj, $text, '$now');
1342 SELECT min(time) FROM smailq WHERE rcpts=$rcpts AND subj=$subj;
1343 EOF
1345 ## XXX: Adhoc load mitigation
1346 case "$now" in
1347 *[01])
1348 err flush_queue=$mintime
1349 smail_queue_flush ;;
1350 esac
1351 fi
1353 setviastring() {
1354 table=par
1355 oifs="$IFS"
1356 IFS="&"
1357 for us in $1; do
1358 k=${us%%=*}
1359 v="`echo ${us#*=}|tr '%+' '= '|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
1360 setpar "$k" "string" "$v"
1361 done
1362 IFS="$oifs"
1364 checkdomain() (
1365 # Check the validity of domain by referring DNS
1366 item=$1
1367 err checkdomain $1
1368 host ${item#*@} 1>&3 2>&3
1369 host ${item#*@} >/dev/null 2>&1
1371 pwcheck() {
1372 # $1=passwd
1373 dbpswd=`getpwfield $user pswd`
1374 encpswd=`mycrypt "$1" "$dbpswd"`
1375 ## err user=$user, pswd=$1, db=$dbpswd, enc=$encpswd
1376 [ x"$dbpswd" = x"$encpswd" ]
1378 mypwhash() {
1379 mycrypt "`cat`" `genrandom 5`
1381 wasureta() {
1382 user=$1
1383 if ! checkdomain $user; then
1384 contenttype; echo
1385 _m4 -D_TITLE_='Invalid email' $layout/title-only.html
1386 echo "ユーザ名($user)には正しいメイルアドレスが必要です。" | html p
1387 putfooter
1388 exit 0
1389 fi
1390 newpswd=`genrandom` # newsalt=`genrandom 5`
1391 #encpswd=`mycrypt "$newpswd" "$newsalt"`
1392 encpswd=`echo $newpswd|mypwhash`
1393 dbsetbyid user $user pswd "$encpswd" && touch $userupdateflag
1394 # Avoid $user substitution with m4, because $url comes from user input.
1395 _m4 -D_PSWD_="$newpswd" -D_URL_="$url" -D_ADMIN_="$admin" \
1396 $msgdir/mail-newaccount.m4 \
1397 | sed "s/_USER_/$user/g" \
1398 | smail "`collectemail $user`" "New Account"
1400 checkauth() {
1401 user=`getpar user`
1402 skc=`getpar skey` # from cookie
1403 [ -z "$user" ] && return 2
1404 ##skey="`getpwfield $user skey`"
1405 if [ -n "$skc" ]; then
1406 if chkskey "$skc"; then
1407 return 0
1408 fi
1409 fi
1410 pswd=`getpar pswd`
1411 quser=`sqlquotestr "$user"`
1412 dbuser=`query "SELECT name FROM user WHERE name=$quser;"`
1413 if [ $? != 0 ]; then # Maybe DB locked
1414 return 4 # 4=server too heavy
1415 elif [ -z "$dbuser" ]; then
1416 err "Login USER failed: [$user]"
1417 return 2 # 2=login fail
1418 elif [ x"$pswd" = x"wasureta" ]; then
1419 wasureta "$user"
1420 return 1 # wasureta error
1421 fi
1422 # dbpswd="`sq $db \"select pswd from passwd where name='$user'\"`"
1423 # putheader; echo; echo user=$user, db=$dbpswd, enc=$encpswd
1424 if pwcheck "$pswd"; then
1425 newsession=`genrandom 34`
1426 if setskey "$user" "$newsession" &&
1427 dbsetbyid user "$user" login "`date '+%F %T'`"; then
1428 gencookie "user=$user" "skey=$newsession"
1429 return 0
1430 else
1431 return 4 # Heavy load??
1432 fi
1433 fi
1434 err "Login failed: [$user]"
1435 return 2 # Password mismatch
1437 showlogin() {
1438 args=`echo $myargs|tr ' ' '+'`
1439 test -z "$args" && resetskey
1440 s4name=${S4NAME:-s4}
1441 ( sed '/^<body/q' $layout/html.m4.html
1442 cat $layout/login.m4.html
1443 echo '</body></html>'
1444 ) | _m4 \
1445 -D_BODYCLASS_="login" \
1446 -D_TITLE_="$s4name" \
1447 -D_SYSNAME_="Welcome to $s4name" \
1448 -D_MYNAME_="$myname${args+?}$args" ${S4CSS:+-D_S4CSS_="$S4CSS"}
1449 exit 0
1451 dologin() {
1452 test -n "$S4WORLD" && syncaccount
1453 checkauth
1454 st=$?
1455 if [ $st != 0 ]; then
1456 contenttype; echo
1457 _m4 -D_USER_="$user" -D_URL_="$url" -D_ADMIN_="$admin" \
1458 -D _LOADAVG_="`uptime|awk '{print $(NF-2)}'|tr -d ,`" \
1459 $msgdir/login-fail-$st.m4.html
1460 showlogin # and EXIT
1461 fi
1464 # Do instant jobs here
1465 dbsetup
1466 trap cleanup INT HUP EXIT TERM PIPE
1467 # trap cleanup INT HUP
1470 cgiinit() {
1471 tmpd=`tmpd=$tmpdir mktempd`
1472 tmpf=$tmpd/stream.$$
1473 tmpfiles=$tmpfiles" $tmpd"
1474 addsession $session
1475 getcookie
1476 case "$REQUEST_METHOD" in
1477 get|GET) s="$QUERY_STRING" ;;
1478 post|POST) ## dd count=$CONTENT_LENGTH bs=1 of=$tmpf 2>/dev/null #slow
1479 ## dd bs=$CONTENT_LENGTH count=1 of=$tmpf # NOT working
1480 # cat > $tmpf # too much?
1481 head -c $CONTENT_LENGTH > $tmpf # safe?
1482 (echo CL=$CONTENT_LENGTH; ls -lF $tmpf) 1>&3
1483 s="`cat $tmpf`"
1484 ;;
1485 esac
1486 case "$CONTENT_TYPE" in
1487 *boundary*)
1488 bndry=${CONTENT_TYPE#*boundary=}
1489 #for us in `LC_CTYPE=C ./mpsplit.rb "$bndry" $tmpd < $tmpf`
1490 for us in `LC_CTYPE=C ./mpsplit.pl "$bndry" $tmpd < $tmpf`
1491 do
1492 k=${us%%\=*}
1493 #echo u=$us
1494 #v="`echo ${us#*=}|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
1495 v="`echo ${us#*=}|unhexize|sed -e 's/\"/\"\"/g'`"
1496 # err k=$k v=$v
1497 case "$k" in
1498 *:filename)
1499 mimetype=`file --mime-type - < "$tmpd/$v"|cut -d' ' -f2`
1500 type='file'; k=${k%:filename}
1501 # DO NOT ALLOW Space and '|' in file names
1502 newv=`echo "$v"|sed 's/[ \|]/X/g'`
1503 if [ x"$v" != x"$newv" ]; then
1505 fi
1506 err "k=[$k] v=[$v]" # debug@2020-05-31
1507 err "`ls -lF $tmpd/$v` MimeType=$mimetype"
1508 case "$mimetype" in
1509 [Ii]mage/x-xcf)
1510 bzip2 "$tmpd/$v"
1511 v=${v}.bz2
1512 ;;
1513 [Ii]mage/x-*|*/vnd.*) ;;
1514 [Ii]mage/[Hh][Ee][Ii][Ff])
1515 if type heif-convert >/dev/null 2>&1; then
1516 vjpg="${v%.heic}.jpg"
1517 err "Conv $v to $vjpg in $tmpd"
1518 convert -quality 75 -resize $maximagexy'>' \
1519 "$tmpd/$v" "$tmpd/$vjpg" >/dev/null 2>&1
1520 v=$vjpg
1521 else
1522 mimetype="Not supported"
1523 fi
1524 ;;
1525 [Ii]mage/*)
1526 mogrify -quality 75 -resize $maximagexy'>' "$tmpd/$v"
1527 err "Mogrified: `ls -lF $tmpd/$v`" # 2020-05-31
1528 ;;
1529 esac
1530 if ! echo "$mimetype" | egrep "$file_accept_egrep" >/dev/null 2>&1
1531 then
1532 replpar text string " *添付できない形式です($v)* $file_warn"
1533 continue
1534 elif [ `wc -c < "$tmpd/$v"` -gt "$filesize_max" ]; then
1535 replpar text string \
1536 " *添付ファイル($v)は${filesize_max}バイト以下にしてください* $file_warn"
1537 continue
1538 fi
1539 ;;
1540 *)
1541 type='string'
1542 ;;
1543 esac
1544 #sq $db "replace into par values('$session', '$k', '$type', \"$v\")"
1545 setpar "$k" "$type" "$v"
1546 done
1547 ;;
1548 *)
1549 setviastring "$s"
1550 ;;
1551 esac
1553 email4group() {
1554 # Get for-$1=group email address(es) for $2...=users
1555 qgrp=`sqlquote "$1"`; shift
1556 users=`for i; do sqlquote "$i"; done`
1557 users=`echo $users|tr ' ' ','`
1558 sql="WITH
1559 grpemails AS (
1560 SELECT gname, user, val email
1561 FROM grp_mem NATURAL JOIN grp_mem_s
1562 WHERE key='email' AND gname=$qgrp),
1563 useremails AS (
1564 SELECT user.name, val email
1565 FROM user
1566 LEFT JOIN user_m
1567 ON user.name=user_m.name AND user_m.key='email')
1568 SELECT DISTINCT coalesce(g.email, u.email, u.name)
1569 FROM useremails u LEFT JOIN grpemails g
1570 ON u.name=g.user
1571 WHERE u.name in ($users);"
1572 query "$sql"
1574 email4groupbyuid() {
1575 # Get for-$1=group email address(es) for $2...=user-ids
1576 g=$1; shift
1577 uids=`echo "$@"`
1578 uids=`echo $uids|tr ' ' ','`
1579 sql="SELECT DISTINCT name FROM user WHERE rowid IN ($uids);"
1580 email4group "$g" `query "$sql"`
1582 myemail4group() {
1583 # Get my email address for $1-specified group
1584 email4group "$1" "$user" | sed -e 1q -e 's/[ ,].*//'
1586 collectmembersbyid() {
1587 # Collect user names of group specified by grid
1588 rid=${1%%[!0-9]*} # Cleaning
1589 query "SELECT user FROM grp_mem \
1590 WHERE gname=(SELECT gname FROM grp WHERE rowid=$rid);"
1592 collectmembersbyid() {
1593 # Collect user names of group name
1594 qgrp=`sqlquote "$1"`
1595 query "SELECT user FROM grp_mem WHERE gname=$qgrp;"
1597 collectgecosesbyid() {
1598 # Collect user gecoses of group
1599 rid=${1%%[!0-9]*} # Cleaning
1600 query<<-EOF
1601 SELECT gecos
1602 FROM gecoses
1603 WHERE name IN (SELECT user FROM grp_mem
1604 WHERE gname=(SELECT gname FROM grp WHERE rowid=$rid));
1605 EOF
1607 collectemail() (
1608 # Collect email addresses for group $1
1609 # If $TEAM is set, filter by team name
1610 # If $EXCEPT is set as username(s) delimited by comma,
1611 # remove $EXCEPT from list: ....NOT IN ($EXCEPT)
1612 for e; do
1613 if isuser "$e"; then
1614 em=`query "select val from user_m where name='$e' and key='email';"`
1615 [ -n "$em" ] && echo "$em" || echo "$e"
1616 else
1617 qgrp=`sqlquote "$e"`
1618 if [ -z "$TEAM" ]; then
1619 gmem="grp_mem"
1620 else
1621 tm=`sqlquote "$TEAM"`
1622 gmem="(SELECT gname, user FROM grp_mem_m WHERE gname='$e' AND key='team' AND val=$tm)"
1623 fi
1624 ex=${EXCEPT:+"AND g.user NOT IN ($EXCEPT)"}
1625 sql="select coalesce(s.val,um.val,g.user) from
1626 $gmem g left join grp_mem_s s
1627 on g.gname=s.gname and g.user=s.user and s.key='email'
1628 left join user_m um on g.user=um.name and um.key='email'
1629 where g.gname=$qgrp $ex;"
1630 ## err CollectEmail: `echo "$sql"`
1631 query "$sql"
1632 fi
1633 done
1635 sendinvitation() (
1636 # $1=email
1637 iss="invite-`date +%s`-$user"
1638 addsession $iss +${memoplimitdays}days # 1 week due date
1639 query "DELETE FROM par WHERE var='invite' AND val='$1';"
1640 query "REPLACE INTO par VALUES('$iss', 'invite', 'string', '$1');"
1641 gecos=`gecos`
1642 name=$user${gecos:+"($gecos)"}
1643 regist="$urlbase?reg+$iss"
1644 _m4 -D_URL_="$urlbase" \
1645 -D_USER_="$name" \
1646 -D_EMAIL_="$1" \
1647 -D_REGIST_="$regist" \
1648 -D_ADMIN_="$admin" \
1649 $msgdir/mail-invite.m4 \
1650 | smail $1 "SNSへの御招待"
1651 return 0
1653 emaildomaincheck() {
1654 case "$1" in
1655 *@*@*) echo "無効なアドレスです"; return 1 ;;
1656 *@*)
1657 local=${1%@*} domain=${1#*@}
1658 if ! host $domain >/dev/null 2>&1; then
1659 echo "ドメイン($domain)が見付かりません。"
1660 return 2
1661 fi
1662 return 0
1663 ;;
1664 *) echo "正しいメイルアドレスをいれてください"; return 3 ;;
1665 esac
1667 invite() {
1668 email=`getpar email | tr '[A-Z]' '[a-z]'`
1669 case "$email" in
1670 *@*@*|*\ *) repo="無効なアドレスです" ;;
1671 *@*)
1672 local=${email%@*} domain=${email#*@}
1673 if ! repo=`emaildomaincheck $email`; then
1674 repo="招待アドレスのエラー: $repo"
1675 elif [ -n "`query \"select * from user where name='$email';\"`" ]; then
1676 repo="$email さんは既に加入しています。"
1677 elif sendinvitation $email; then
1678 repo="アドレス($email)宛に案内を送信しました。"
1679 else # Cannot be reached here
1680 repo="自動登録できない状況です。管理者に依頼してください。"
1681 fi ;;
1682 "") repo="招待したい人のメイルアドレスを入力してください。" ;;
1683 *) repo="無効なアドレスです" ;;
1684 esac
1685 addr=`query "select val from par where sessid like 'invite-%-$user';"`
1686 if [ -n "$addr" ]; then
1687 susp="<h2>招待済みで加入待ちのアドレス</h2><pre>$addr</pre>"
1688 fi
1689 if [ -f $invite_policy ]; then
1690 pol="spaste(\`$invite_policy')"
1691 else
1692 pol="$invite_policy"
1693 fi
1694 _m4 -D_TITLE_="招待" -D_REPORT_="\`$repo'" -D_ACTION_="?invite" \
1695 -D_BODYCLASS_="default" -D_SUSPENDED_="$susp" \
1696 -D_INVITE_POLICY_="$pol" \
1697 $layout/html.m4.html $layout/invite.m4.html
1699 regist() {
1700 # $1=session-id-for-invitation
1701 _m4 -D_TITLE_="Invitation" $layout/html.m4.html
1702 if [ -z "$1" ]; then
1703 echo "bye bye" | html p
1704 reutrn
1705 fi
1706 email=`session=$1 getpar invite | tr '[A-Z]' '[a-z]'` # Ensure lower case
1707 if [ -z "$email" ];then
1708 cat<<EOF
1709 <p>無効な招待状チケットです。</p>
1710 <p>招待状の有効期限(1週間)が切れているか、チケット番号が異なっています。
1711 加入している人に、再度招待してもらいましょう。</p>
1712 EOF
1713 return
1714 fi
1715 echo "$email さんようこそ" | html h2
1716 query "replace into user values('$email');"
1717 # Fake login password to wasureta
1718 query "replace into par values('$session', 'pswd', 'string', 'wasureta'),
1719 ('$session', 'user', 'string', '$email');"
1720 wasureta $email
1721 echo "このアドレスに初期パスワードを送信しました。" |html p
1722 echo "新着メイルを確認してログインしてください。" |html p
1723 addsession $1 # for removal after 1 minute
1724 _m4 -D_SYSNAME_="Initial Login" -D_MYNAME_="$myname?userconf" \
1725 $layout/login.m4.html
1726 return
1728 group_safename() {
1729 # Convert $1 to safe group name
1730 echo "$1" | tr -d '"'"'",
1732 groupupdate() {
1733 gname=`getpar gname`
1734 qgname=`sqlquote "$gname"`
1735 if [ -n "$gname" ]; then
1736 # See ALSO same job in showgroup()
1737 newgname=`group_safename "$gname"`
1738 err newgname=$newgname
1739 if [ x"$newgname" != x"$gname" ]; then
1740 err NewGNAME: gname=$newgname
1741 gname=$newgname
1742 echo "使用禁止文字を除去し $gname としました。" | html p
1743 replpar gname string "$gname"
1744 fi
1745 # Name confliction check
1746 parow=`getpar rowid`
1747 ## err parow=$parow
1748 qgname=`sqlquote "$gname"` # Set again in case gname modified
1749 query "BEGIN EXCLUSIVE;"
1750 ## err "select count(gname) from grp where rowid != ${parow:-0} and gname = $qgname;"
1751 count=$(query "select count(gname) from grp where rowid != ${parow:-0} and gname = $qgname;")
1752 if [ $count -gt 0 ]; then
1753 echo "そのグループ名は既にあります。" | html p
1754 query "END;"
1755 return
1756 fi
1757 par2table $formdir/grp.def
1758 query "END TRANSACTION;"
1759 # Remove orphan
1760 : <<EOF
1761 select a.id,b.val from (select * from blog where id in
1762 (select id from blog_s where key='owner'
1763 and val not in (select name from user union select gname from grp)))
1764 a left join blog_s b on a.id=b.id and b.key='owner';
1765 EOF
1766 rm=`getpar rm` cfm=`getpar confirm`
1767 ## err groupupdate:::: after par2tbl rmcfm=$rm$cfm
1768 if [ x"$rm$cfm" = x"yesyes" ]; then
1769 if [ -z "`query \"select gname from grp where gname=$qgname;\"`" ]; then
1770 sql="delete from blog where id in
1771 (select id from blog_s where key='owner' and val=$qgname);"
1772 err rm-grp cleaning sql=`echo $sql`
1773 query "$sql";
1774 unsetpar tag kwd
1775 grps # When removing a group, switch to grp-list
1776 return # and return
1777 fi
1778 fi
1779 [ -z "$parow" ] && joingrp "$gname" "$user" yes "" as-admin
1780 fi
1781 sql="select rowid from grp where gname=$qgname;"
1782 grid=$(query $sql)
1783 ## err grpupdate:new-grid=$grid, sql=$sql
1784 grp $grid
1786 groupclone() {
1787 # $1=grp-rowid of clone-base group
1788 rid=${1%%[!0-9]*} # Cleaning
1789 case "$1" in
1790 */noteam)
1791 noteam="AND key != 'team'" ;;
1792 esac
1793 qgrp=`query "SELECT quote(gname) FROM grp WHERE rowid=$rid;"`
1794 if [ -z "$qgrp" ]; then
1795 echo "無効なグループIDです($1)" | html p
1796 return
1797 fi
1798 if ! isgrpownerbygid "$user" "$rid"; then
1799 echo "グループ管理者のみがクローン可能です" | html p
1800 return
1801 fi
1802 i=0
1803 while true; do
1804 copy="-copy$i"
1805 newqname=`query "SELECT quote($qgrp || '$copy');"`
1806 # err Trying new grp=$newqname with copy=$copy
1807 test=`query "SELECT gname FROM grp WHERE gname=$newqname;"`
1808 if [ -n "$test" ]; then
1809 i=$((i++))
1810 continue
1811 fi
1812 break
1813 done
1814 # Creating New group "$newqname" with members of old group
1815 # err Creating new grp=$newqname with copy=$copy
1816 query<<-EOF
1817 BEGIN;
1818 INSERT INTO grp VALUES($newqname); -- Create NEW one
1819 REPLACE INTO grp_s(gname, key, val) -- Copy tag
1820 SELECT $newqname, key, val
1821 FROM grp_s WHERE gname=$qgrp AND key IN ('tag', 'mode');
1822 REPLACE INTO grp_s(gname, key, type, val) -- Copy gecos with "copy$n"
1823 SELECT $newqname, key, type, val || '$copy'
1824 FROM grp_s WHERE gname=$qgrp AND key='gecos';
1825 -- Copy members and their configuration --
1826 REPLACE INTO grp_mem SELECT $newqname, user
1827 FROM grp_mem WHERE gname=$qgrp;
1828 REPLACE INTO grp_mem_s SELECT $newqname, user, key, type, val, bin
1829 FROM grp_mem_s WHERE gname=$qgrp;
1830 REPLACE INTO grp_mem_m SELECT $newqname, user, key, type, val, bin
1831 FROM grp_mem_m WHERE gname=$qgrp $noteam;
1832 -- Copy administrators --
1833 REPLACE INTO grp_adm SELECT $newqname, user
1834 FROM grp_adm WHERE gname=$qgrp;
1835 COMMIT;
1836 EOF
1837 newrowid=`query "SELECT rowid FROM grp WHERE gname=$newqname;"`
1838 STOPCLONEMSG=1 groupconf "$newrowid"
1840 groupman() {
1841 note="<p>グループ名に使用できない文字は自動的に削除されます。</p>"
1843 GF_STAGE="grpconf"
1844 GF_STAGE=groupupdate
1845 DT_VIEW=grp dumptable html grp 'gname gecos:DESC mtime:TIME' 'order by b.TIME desc' \
1846 |_m4 -D_TITLE_="グループ作成" \
1847 -D_FORM_="$note`genform $formdir/grp.def`" \
1848 -D_DUMPTABLE_="syscmd(cat)" \
1849 $layout/html.m4.html $layout/form+dump.m4.html
1851 userconf() {
1852 [ -n "`getpar rowid`" ] && par2table $formdir/user.def
1853 _m4 -D_BODYCLASS_=userconf -D_TITLE_="ユーザ情報編集" $layout/html.m4.html
1854 GF_ACTION="?home" edittable "$formdir/user.def" "user" "$user"
1856 groupconf() {
1857 # $1=rowid in grp (2015-07-21 changed from gname)
1858 [ -n "`getpar rowid`" ] && par2table $formdir/grp.def
1859 _m4 -D_BODYCLASS_=groupconf -D_TITLE_="グループ情報編集" $layout/html.m4.html
1860 #rowid=`query "select rowid from grp where gname='$1';"`
1861 rowid=${1%%[!A-Z0-9a-z_]*}
1863 ### If user is not admin, lead to group home
1864 grp=`getgroupbyid $rowid`
1865 if ! isgrpowner "$user" "$grp"; then
1866 echo "<p><a href=\"?grp+$rowid\">`echo "$grp"|htmlescape`</a></p>"
1867 return
1868 fi
1870 # GF_ACTION="?grp+$1" edittable "$formdir/grp.def" "grp" "$rowid" #2015-0804
1871 GF_STAGE="groupupdate" edittable "$formdir/grp.def" "grp" "$rowid"
1872 if [ -z "$STOPCLONEMSG" ]; then
1873 ## Setup migration menu
1874 height="10em" ## Ugly!!
1875 if [ -n "$S4WORLDLIST" ]; then
1876 v=`fgrep -v "value=\"$worldconf\"" $worldoptionfile`
1877 err v=$v
1878 if [ -n "$v" ]; then
1879 migrate=$(cat<<-EOF
1880 `cgi_radio grpaction migrate id="migrate"`<label
1881 for="migrate">別Worldへ移住</label>
1882 <div style="height: $height;">
1883 <form action="?migrategrp">
1884 <p>移住先:<select name="migrateto">$nl$v$nl</select></p>
1885 <p>グループや掲示板のURLが変わります。
1886 外部からリンクしている場合は飛べなくなります。
1887 すでにリンクされた掲示板を多数含む場合は既存グループを温存し、
1888 「グループのクローン」で
1889 メンバーを引き継いだ上でそのクローンを移住するのがお勧めです。</p>
1890 <p><label>`cgi_checkbox emichk yes`確認</label></p>
1891 `cgi_hidden stage migrategrp`
1892 `cgi_hidden rowid $rowid`
1893 `cgi_submit OK`
1894 `cgi_reset Reset`
1895 </form>
1896 </div>
1897 EOF
1899 fi
1900 fi
1901 html div 'class="foldtabs"' <<-EOF
1902 `cgi_radio grpaction clone id="clone"`<label
1903 for="clone">グループのクローン作成</label>
1904 <div style="height: $height;">
1905 <p>構成メンバーが同じ新規グループを作成します。</p>
1906 <table>
1907 <tr><td><a href="?groupclone+$rowid">
1908 <button>クローン作成(チームも複製)</button></a></td>
1909 <td><p>(チームなどもそのままで掲示板なしの状態から)</p></td></tr>
1910 <tr><td><a href="?groupclone+$rowid/noteam">
1911 <button>作成(チームなし)</button></a></td>
1912 <td>(チームは引き継がずメンバーのみ同じグループを作成)</td></tr>
1913 </table>
1914 <p>ボタンを押すと即作成します。不要な場合はグループ編集で
1915 削除してください。</p>
1916 </div>
1917 $migrate
1918 `cgi_radio grpaction close id="x"`<label for="x" accesskey="x">×</label>
1919 <div style="height: $height; background: transparent;"></div>
1920 EOF
1921 fi
1923 migrategrp() {
1924 rowid=`getpar rowid`
1925 rowid=${rowid%%[!0-9]*}
1926 grp=`getgroupbyid $rowid`
1927 if ! isgrpowner "$user" "$grp"; then
1928 echo "<p><a href=\"?grp+$rowid\">`echo "$grp"|htmlescape`</a></p>"
1929 return
1930 fi
1931 if [ x`getpar emichk` != x"yes" ]; then
1932 echo "移住確認未チェックなので中止します。" | html p
1933 grp "$rowid"
1934 return
1935 fi
1936 destconf=`getpar migrateto`
1937 err destconf=$destconf
1938 if [ ! -e $destconf ]; then
1939 echo "移住先Worldが認識できないので中止します($destconf)。" | html p
1940 grp "$rowid"
1941 return
1942 fi
1943 if [ -n "$worldconf" ]; then
1944 srcconf=$worldconf
1945 else
1946 srcconf=s4-config.sh
1947 fi
1948 _m4 -D_TITLE_="移住操作" -D_BODYCLASS_="" $layout/html.m4.html
1949 echo "移住操作" | html h1
1950 echo '<pre>'
1951 set -- "$srcconf" "$destconf" "$rowid"
1952 err ./s4-migrate.sh "$srcconf" "$destconf" "$rowid"
1953 . ./s4-migrate.sh # Dot(.) sourcing might not pass arguments
1954 rc=$?
1955 echo "</pre>"
1956 if [ $rc -eq 0 ]; then
1957 echo "World [$world] への移住完了。" | html p
1958 echo "<p><a href=\"$dsturl?grp+$destrowid\">移住先</a></p>"
1959 clean-orphaned
1960 else
1961 echo "移住失敗" | html p
1962 echo "移動先に重複がないか確認して下さい。" | html p
1963 fi
1964 return
1966 mems() {
1967 _m4 -D_TITLE_="参加者一覧" -D_BODYCLASS_=listmember $layout/html.m4.html
1968 kwd=`getpar kwd`
1969 listmember $kwd
1971 grps() {
1972 case "$S4WORLD" in
1973 $nonewgroupworld) ;;
1974 *) LINK_NEWGRP="<a href=\"?groupman\">新規グループ作成</a>" ;;
1975 esac
1976 _m4 -D_TITLE_="グループ一覧" -D_BODYCLASS_=listgroup $layout/html.m4.html
1977 kwd=`getpar kwd`
1978 listgroup "$kwd" \
1979 | _m4 -D_DUMPTABLE_="syscmd(cat)" \
1980 -D_TITLE_="グループ関連操作" \
1981 -D_FORM_="${LINK_NEWGRP}${NEWGRP_GUIDE}" \
1982 $layout/form+dump.m4.html
1984 grp() { # $1=group-rowid
1985 gpg=`getpar grp`
1986 grid=${1:-$gpg}
1987 grp=`getgroupbyid "$grid"`
1988 ## . ./s4-blog.sh
1989 jg=`getpar joingrp`
1990 if [ -n "$jg" ]; then
1991 [ -n "$jg" -a -n "$grp" ] &&
1992 joingrp "$grp" "$user" "$jg" "`getpar email`"
1993 fi
1994 htmlheader=$layout/html.m4.html
1995 showgroup "$grid"
1997 sql4interestblogs() {
1998 cat<<EOF
1999 CREATE TEMPORARY VIEW interestblogs AS
2000 SELECT blog.rowid rid, id, author
2001 FROM blog
2002 NATURAL JOIN
2003 (SELECT id, val owner FROM blog_s WHERE key='owner') bs
2004 WHERE CASE WHEN (SELECT name FROM user where name=bs.owner) IS NOT NULL
2005 THEN 1 -- blog owner is an user, READABLE
2006 WHEN (SELECT user FROM grp_mem
2007 WHERE gname=bs.owner AND user='$user') IS NULL
2008 THEN 0
2009 ELSE 1
2010 END;
2011 EOF
2013 listnewblogsql() { # $1=user
2014 newdays=${WHATS_NEW_DAYS##*[!0-9]} # Shave non-digits
2015 newdays=${newdays%%[!0-9]*}
2016 newdays=${newdays:-14}
2017 basetime="datetime('now', 'localtime', '-${newdays} days')"
2018 deftime=`query "SELECT coalesce((SELECT max(time) FROM acclog
2019 WHERE user='$user'
2020 AND tblrowid IN
2021 ($blogreadflagrowid,
2022 $blogcutoffflagrowid)),
2023 $basetime -- "0"
2024 );"`
2025 cat<<EOF
2026 `sql4interestblogs`
2027 WITH article_ctime as (
2028 SELECT id,blogid,author,max(val) ctime
2029 FROM article join article_s s using(id)
2030 WHERE s.key='ctime' AND s.val > '$deftime'
2031 GROUP BY id
2032 ), blog_title_owner as (
2033 SELECT blg.rid brid, id,
2034 max(case key when 'title' then val end) title,
2035 max(case key when 'owner' then val end) owner
2036 FROM interestblogs blg, blog_s using(id) group by id
2037 ), blogall as (
2038 SELECT * FROM blog_title_owner b JOIN article_ctime ac ON b.id=ac.blogid
2039 ), news as (
2040 SELECT brid, bl.id blid, bl.title, ctime,
2041 coalesce(al.time, '$deftime') atime,
2042 count(bl.id) "新着", bl.author
2043 FROM blogall bl
2044 LEFT JOIN
2045 (SELECT * FROM acclog WHERE user='$user' AND tbl='blog') al
2046 ON bl.brid=al.tblrowid
2047 WHERE atime < bl.ctime
2048 GROUP by bl.id ORDER BY ctime desc,"新着" desc, bl.id
2049 LIMIT 10
2050 ) SELECT brid LINK, "新着",
2051 (SELECT count(*) FROM article WHERE blogid=blid) "総数",
2052 ctime, title,
2053 (SELECT gecos FROM gecoses WHERE name=author) gecos
2054 FROM news;
2055 EOF
2058 search_form() {
2059 # $1 = { author=<AUTHOR> | grid=<GroupRowid> }
2060 # $2(optional) = pre-input keywords
2061 help="(1)空白区切りの単語で本文検索
2062 (2)@YYYY-MM-DD 日付け(シェルパターン可)で日付け検索
2063 @2016-0[1-6] → 2016年1月から6月
2064 @>2016-01 @<2016-02-15 → 2016年1月から2月14日までの期間
2065 @week → 最近一週間
2066 (3)#番号 で記事ID検索
2067 (1)と(2)は組み合わせOK
2068 例: @2016-10-0[1-9] 芋煮
2069 → 2016年10月上旬でキーワード「芋煮」を含む記事検索
2070 ※クイズ板は検索対象から外されます。"
2071 auth=""
2072 placeholder="全記事からの検索"
2073 case "$1" in
2074 author=*)
2075 a=`echo "${1#author=}"|htmlescape`
2076 g=`gecos ${1#author=}`
2077 auth="<input type=\"hidden\" name=\"author\" value=\"$a\">"
2078 placeholder="このユーザの書込検索"
2079 help="★★ $g さんの書き込みから検索します$nl$help"
2080 ;;
2081 grid=*)
2082 a=`echo "${1#grid=}"`; a=$((0 + $a))
2083 auth="<input type=\"hidden\" name=\"grid\" value=\"$a\">"
2084 placeholder="このグループからの検索"
2085 ;;
2086 esac
2087 inikwd="$2" # no need to htmlescape
2088 cat<<-EOF
2089 <div class="right">
2090 <form action="$myname">$auth
2091 <input type="text" name="kwd" value="$inikwd" title="$help"
2092 placeholder=" $placeholder " width="10" accesskey="k">
2093 <!-- POST SENTENCE -->
2094 ${touchpanel:+<p class="help">$help</p>}
2095 <input type="hidden" name="stage" value="searchart">
2096 <!-- EOF -->
2097 </form>
2098 </div>
2099 EOF
2102 imgsrc_cache() (
2103 # $1 = directory for cache'ing
2104 # $2 = table (user_m or grp_m)
2105 # $3 = keycond (was: condition for choosingowner)
2106 # $4 = size : S = Small, M = Medium, O = Original
2107 dir="$1" tbl="$2"
2108 keycond="$3"
2109 whos="$keycond AND key='profimg' AND type LIKE 'file:image%'
2110 ORDER BY rowid DESC LIMIT 1"
2111 [ -d "$dir" ] || mkdir -p "$dir"
2112 tmpf=$tmpd/imgsrc_cache.$$
2113 case "$4" in
2114 [Ss]) size=S ;;
2115 [Oo]) size=O ;;
2116 *) size=M ;;
2117 esac
2118 # ImageCache filename storing schema:
2119 # <table_s>.{key, val}={"profimgcache_S", "$cacheimg_S"}
2120 sql0="SELECT val || '//' || type FROM $tbl WHERE $whos;"
2121 sql1="SELECT hex(bin) FROM $tbl WHERE $whos;"
2122 valtype=`query "$sql0"`
2123 filename=${valtype%%//*}
2124 filetype=${valtype##*//file:}
2125 if [ x"$filename" = x"${filename%.*}" ]; then
2126 # If nor filename extension found, set it to image type
2127 case "$filetype" in
2128 image/*) filename=$filename.${filetype#image/} ;;
2129 esac
2130 fi
2131 cacheimg_S=$dir/S_$filename
2132 cacheimg_M=$dir/M_$filename
2133 cacheimg_O=$dir/$filename
2134 cacheimg=$dir/${size}_$filename
2135 sumfile="$dir/$filename.sum"
2136 sum=`query "$sql1" | tee $tmpf | encode` # encode() is maybe sha1
2137 if test -s "$sumfile" && [ x"`cat \"$sumfile\"`" = x"$sum" ] \
2138 && test -s "$cacheimg_S" && test -s "$cacheimg_M" ; then
2139 # if cache is fresh and has the same checksum,
2140 echo "<img src=\"$cacheimg\">"
2141 else
2142 fifo=`mktemp "$tmpf.fifo.XXXXXXX"`
2143 rm -f $fifo # Safe, because $tmpf is in mktemp dir.
2144 fifo2=$fifo.2
2145 mkfifo $fifo $fifo2
2146 fmt=${filename##*.}
2147 ## [[ NOTE ]]
2148 ## a. convert oldimage newimage
2149 ## b. convert oldimage fmt:- | convert - newimage
2150 ## b is much smaller than a
2151 cat $tmpf | unhexize \
2152 | tee $fifo \
2153 | convert -define ${fmt}:size=${iconxy_M} \
2154 -resize ${iconxy_M}'>' - ${fmt}:- \
2155 | tee $fifo2 \
2156 | convert - "$cacheimg_M" &
2157 cat $fifo | convert -define ${fmt}:size=${iconxy_S} \
2158 -resize ${iconxy_S}'>' - ${fmt}:- \
2159 | convert - "$cacheimg_S" &
2160 printf '%s' "<img src=\"data:${filetype},"
2161 hexize "$fifo2" |sed 's/\(..\)/%\1/g' # Use medium as pre-cached image
2162 echo '">'
2163 echo "$sum" > $sumfile
2164 fi
2165 ## Now preparing cache image, done.
2166 ## Store this information to DB
2167 stbl=${tbl%_m}_s # user_s or grp_s
2168 pkey=${keycond%%=*} # Primary Key name
2169 pval=${keycond#*=} # Primary Key value
2170 query <<-EOF
2171 REPLACE INTO $stbl($pkey, key, type, val)
2172 VALUES($pval, '$iconcachekey', 'string', `sqlquote "$cacheimg_S"`);
2173 EOF
2176 showhome() {
2177 # $1=userRowIdToShow
2178 err showhome \$1=$1
2179 case "$1" in
2180 *@*) uname=`getvalbypkey user name "$1"` ;;
2181 *) uname=`getvalbyid user name $1` ;;
2182 esac
2183 ## err ShowHome: uname=$uname
2184 td=`getcachedir home/"$1"`
2185 gecos=`gecos "$uname"`
2186 ## err SH:gecos=$gecos
2187 GF_VIEWONLY=1
2188 cond="gname in (select gname from grp_mem where user='$uname')"
2189 search_form_args=""
2190 if [ x"$user" = x"$uname" ]; then
2191 if [ -z "$S4MASTERDB" ]; then
2192 usermenu="<a href=\"?userconf\" accesskey=\"e\"
2193 title=\"Shortcut: E${nl}Edit Profile\">プロフィールの編集</a> / "
2194 elif [ -n "$S4MASTERURL" ]; then
2195 usermenu="<a href=\"$S4MASTERURL\" accesskey=\"e\"
2196 title=\"Shortcut: E${nl}Main Site\">Base World</a> / "
2197 fi
2198 usermenu="$usermenu
2199 <a href=\"?blog\" accesskey=\"n\" title=\"Shortcut: N${nl}New blog\">新規話題の作成</a>"
2200 # Display folders
2201 sql="select count(id) from article_m where id
2202 in (select id from article where author='$user')
2203 and type like 'file:%';"
2204 ## err nfile-sql=`echo "$sql"`
2205 nfile=`query "$sql"`
2206 # err nfile=$nfile
2207 if [ $nfile -gt 0 ]; then
2208 usermenu="$usermenu / <a href=\"?lsmyfile\" accesskey=\"l\"
2209 title=\"Shortcut: L${nl}List All Attachment files\">過去の提出ファイル</a>"
2210 fi
2211 else
2212 latestlog=`query "SELECT max(time) FROM acclog WHERE user='$uname' \
2213 GROUP BY user;"`
2214 usermenu="<p>Last seen on $latestlog</p>"
2215 search_form_args="author=$uname"
2216 fi
2217 . ./s4-blog.sh
2219 tf=$tmpd/title.$$ pf=$tmpd/profile.$$ bf=$tmpd/blogs.$$ sf=$tmpd/search.$$
2220 search_form "$search_form_args" > $sf
2221 printf "%s さん%s" "$gecos" "${S4WORLDNAME:+@$S4WORLDNAME}" \
2222 | htmlescape > $tf
2223 { echo "<div class=\"noprofimg\">"
2224 viewtable $formdir/user.def user $1
2225 echo "</div>"
2226 } > $pf
2228 sqcond="WHERE name='$uname' AND key='profimg' AND type LIKE 'file:image%'"
2229 img=`query "SELECT type FROM user_m $sqcond LIMIT 1;"`
2230 imf=$tmpd/profimg.$$; touch $imf
2231 if [ -n "$img" ]; then
2232 if true; then
2233 tbl=user_m
2234 enticond="name='$uname'"
2235 imgsrc_cache "$td/main" user_m "$enticond" M
2236 else
2237 { printf '%s' "<IMG src=\"data:${img#file:},"
2238 query "SELECT hex(bin) FROM user_m $sqcond ORDER BY rowid LIMIT 1;" \
2239 | sed 's/\(..\)/%\1/g'
2240 echo '">'
2242 fi > $imf
2243 fi
2244 nblog=`query "SELECT count(id) FROM blog_s WHERE key='owner' AND \
2245 val='$uname';"`
2246 ## REMOVE This comment block until 2019/7/1
2247 ## err "----- `gdate +%FT%T.%3N` ------------C"
2248 ## [ x"$user" = x'yuuji@gentei.org' ] && ddd=1
2249 listblog $uname > $bf
2250 ## unset ddd
2251 ## err "----- `gdate +%FT%T.%3N` ------------D"
2253 hometail=$tmpd/tail.$$
2254 mkfifo $hometail
2256 #Calling listgroupbytable, originally here
2259 # Display Most Recent Entry
2260 shortval=${dumpcollen:+"substr(val, 0, $dumpcollen)"}
2261 shortval=${shortval:-val}
2263 # The m.aid in the next line is suspicious. But works fine in SQLite3...
2264 DT_SQL="SELECT b.rowid || '#' || m.aid LINK,
2265 ctime,
2266 (SELECT $shortval FROM blog_s WHERE key='title' AND id=b.id) title,
2267 (SELECT gecos FROM gecoses
2268 WHERE name=(SELECT val FROM blog_s
2269 WHERE key='owner' AND id=b.id)) owner,
2270 (SELECT $shortval val FROM article_s WHERE id=m.aid AND key='text') text
2271 FROM blog b
2272 JOIN
2273 (SELECT distinct blogid, a.id aid, max(val) ctime
2274 FROM article a, article_s s
2275 ON a.id=s.id AND a.author='$uname' AND s.key='ctime'
2276 GROUP BY blogid ORDER BY val DESC LIMIT 50
2277 ) m
2278 ON b.id=m.blogid;"
2279 # This should be as follows
2280 : <<EOF
2281 WITH arts AS(
2282 SELECT (SELECT rowid FROM blog WHERE id=a.blogid) brid,
2283 a.blogid, a.id id, s.val ctime
2284 FROM article a NATURAL JOIN article_s s
2285 WHERE s.key = 'ctime' AND a.author='$user'
2286 GROUP by s.id
2288 SELECT a0.brid,a0.blogid,a0.id,a0.ctime
2289 FROM arts a0
2290 JOIN
2291 (SELECT blogid,max(ctime) mct FROM arts a1 GROUP BY blogid) a1
2292 ON a0.blogid=a1.blogid AND a0.ctime=a1.mct
2293 ORDER BY ctime DESC LIMIT 50;
2294 EOF
2296 cat<<-EOF
2297 `cgi_radio foldtabs yes 'id="mre" accesskey="d"'`<label
2298 for="mre" title="Shortcut: D${nl}Recent Post">最近の書き込み先</label>
2299 <div class="lcto">
2300 `DT_VIEW=replyblog dumptable html blog`
2301 </div>
2302 EOF
2303 unset DT_SQL
2304 if [ x"$user" = x"$uname" ]; then
2305 # Display NEWS
2306 # 2016-06-26
2307 if [ x"`getpar readchk``getpar read`" = x"yesyes" ]; then
2308 acclog blog $blogreadflagrowid
2309 # echo "全部既読にしました" | html p
2310 fi
2311 # 2016-02-19 Counting NEWS without using dumptable.
2312 sql=`listnewblogsql "$user"`
2313 # echo "$sql" > tmp/listnew
2314 new10=`DT_SQL="$sql" DT_VIEW=replyblog dumptable html blog`
2315 cont=`echo "$new10"|grep "^<TR>"|wc -l`
2316 cont=$((cont-1))
2317 err newcount=$cont
2318 if [ $cont -gt 0 ]; then
2319 #echo "全体の新着記事${cont}傑" | html h2
2320 cgi_radio foldtabs yes 'id="new10" accesskey="f"'
2321 echo "<label for=\"new10\" title=\"Shortcut: F${nl}NEWS\">新着${cont}傑</label><div>"
2322 cat<<-EOF | html form 'action="?home"'
2323 `cgi_checkbox readchk yes 'id="read"'`<label
2324 for="read">新着ふくめて全部読んだことにする</label>
2325 `cgi_submit '確定'`
2326 `cgi_hidden read yes`
2327 EOF
2328 echo "$new10 <!-- new10 -->"
2329 echo "</div>"
2330 else # If news is 0, set log cut off flag
2331 acclog blog $blogcutoffflagrowid # for speed
2332 fi
2333 else # Not My Home ($user != $uname)
2334 : # DT_SQL=
2335 fi
2336 ) > $hometail & # Is background call safe to m4??
2338 listgroupbytable $formdir/grp.def "$cond" $uname |
2339 _m4 -D_BODYCLASS_=home -D_TITLE_="spaste(\`$tf')" \
2340 -D_PROFILE_="spaste(\`$pf')$usermenu" \
2341 -D_PROFIMG_="spaste(\`$imf')" \
2342 -D_BLOGS_="spaste(\`$bf')" \
2343 -D_SEARCH_="spaste(\`$sf')" \
2344 -D_NBLOG_="$nblog" \
2345 -D_GROUPS_="syscmd(\`cat')" \
2346 -D_HOMETAIL_="syscmd(\`cat $hometail')" \
2347 $layout/html.m4.html $layout/home.m4.html
2349 # Record access log
2350 [ -n "$1" ] && [ x"$1" != x"$user" ] && acclog user $1
2352 commission() { # $1=grp-rowid $2=user-rowid
2353 contenttype; echo
2354 ## err commission: "$@"
2355 gname=`getgroupbyid $1`
2356 echo "グループ $gname 管理者委任" \
2357 | _m4 -D_TITLE_="syscmd(\`cat')" $layout/html.m4.html
2358 if [ -n "$2" ]; then
2359 grp_reg_adm "$@"
2360 else
2361 echo "無効な指定です。普通のアクセスならここに来ないはず。"|html p
2362 fi
2364 listgroupbytable() {
2365 # $1=deffile $2...=condition $3(optional)=uname
2366 tagline=`grep :tag: $1`;
2367 and="${2:+and }" where=${2:+where }
2368 href="<a href=\"$myname?grp+"
2369 echo '<div class="listgroup">'
2370 sql="select val from grp_s where key='tag' $and$2 group by val;"
2371 ## err ListGRP: query sql="$sql"
2372 for tag in `query "$sql"`
2373 do
2374 ## err ListGrp: tag=$tag
2375 tn=${tagline%%=${tag}*}
2376 tn=${tn##*[ :]}
2377 sql="select rowid||':'||gname as 'グループ名',説明 from
2378 (select (select rowid from grp g where g.gname=grp_s.gname)
2379 as rowid,
2380 gname,
2381 max(case key when 'gecos' then val end) as '説明',
2382 max(case key when 'tag' then val end) as 'tag',
2383 max(case key when 'mtime' then val end) as mtime from grp_s
2384 $where$2 group by gname having tag='$tag' order by mtime desc);"
2385 ## err PersonalGroupList= `echo $sql`
2386 echo "<h2>$tn</h2>"
2387 echo '<table class="b listgroup">'
2388 sq -header -html $db "$sql" \
2389 | sed "s,\(<TR><TD>\)\([0-9]*\):\([^<]*\)</TD>,\1$href\2\">\3</a>,"
2390 echo '</table>'
2391 done
2392 if [ -n "$S4WORLDLIST" -a -n "$3" ]; then
2393 err "peekgrpworlds($user) BEGIN: `gdate +%S.%03N`"
2394 peekgrpworlds mem:"$3"
2395 err "peekgrpworlds($user) END: `gdate +%S.%03N`"
2396 fi
2397 echo '</div>'
2399 iconhref() (
2400 # $1=icon-file, $2=Href $3=title $4...=anchor
2401 data=`percenthex "$1"`
2402 ct=`file --mime-type - < "$1"|cut -d' ' -f2`
2403 ## err iconhref: \$1=$1 \$2=$2 \$3="$@"
2404 href=$2; title=$3; shift 3
2405 echo "<a href=\"$href\"><img title=\"$title\" src=\"data:$ct,$data\">$@</a>"
2407 iconhref2() (
2408 # $1=icon-file, $2=Href $3=title $4...=anchor
2409 src=$1
2410 href=$2; title=$3; shift 3
2411 anchor=`echo $@|htmlescape`
2412 echo "<a href=\"$href\"><img title=\"$title\" src=\"$src\">$anchor</a>"
2414 listentry() (
2415 # $1=user/group $2=SearchKeyword $3=condition(if any) $4=grprowid(if in grp)
2416 # Referring variable $iamowner=$grp to attach owner-request links
2417 ## err listentry: \$1=$1 \$2=$2 \$3=$3
2418 cond='' hiddens=''
2419 offset=`getpar offset`; offset=${offset%%[!0-9]*}
2420 if [ -z "$offset" ]; then
2421 offset=`getpar start`; offset=${offset%%[!0-9]*}
2422 offset=$((offset-1))
2423 fi
2424 offset=$((offset + 0)) # change to numeric forcibly
2425 [ $offset -lt 0 ] && offset=0
2426 limit=$listentlimit
2427 dir=`getcachedir "$1"`
2428 if [ x"$1" = x"user" ]; then
2429 hrb="$myname?home"
2430 deficon=person-default.png
2431 entity="ユーザ" tbl=user link=rowid nm=name # stage=mems
2432 [ -n "$4" ] && hiddens=`cgi_hidden grid $4`
2433 gcs=gecos
2434 else # if group
2435 hrb="$myname?grp"
2436 deficon=group-default.png
2437 entity="グループ" tbl=grp link=rowid nm=gname stage=grps
2438 gcs=name
2439 tagline=`grep :tag: $formdir/grp.def|cut -d: -f5-`
2440 if [ -n "$tagline" ]; then
2441 tagconv=`echo $tagline|sed 's/\([^= :]*\)=\([^= :]*\)/-D\2=\1/g'`
2442 ## err tagconv=$tagconv
2443 fi
2444 fi
2445 if [ ! -d $dir ]; then
2446 mkdir -p $dir
2447 fi
2448 if [ ! -s $dir/$deficon ]; then
2449 convert -geometry $thumbxy $imgdir/$deficon $dir/$deficon
2450 fi
2451 kwd=${2:-`getpar kwd`}
2452 if [ -n "$kwd" ]; then
2453 kwd=`echo $kwd | tr -d '";\n' | tr -d "'"`
2454 case "$kwd" in
2455 mem:*@*)
2456 byuser=${kwd#*mem:}
2457 qusr=`sqlquote "$byuser"`
2458 cond1="(a.gname IN (SELECT gname FROM grp_mem WHERE user=$qusr))"
2459 ;;
2460 esac
2461 if [ x"$1" = x"group" ]; then
2462 if [ -n "$cond1" ]; then
2463 enthead="`gecos "$byuser"|htmlescape` さんの所属"
2464 else
2465 cond1="(b.name like '%${kwd}%')"
2466 fi
2467 else
2468 cond1="(nick like '%${kwd}%' or b.name like '%${kwd}%')"
2469 fi
2470 fi
2471 tag=`getpar tag` tag2=`getpar tag2`
2472 if [ x"$tag" = x"NULL" ]; then
2473 tag="" tag2=""
2474 fi
2475 if [ -n "$tag$tag2" ]; then
2476 tag=${tag:-$tag2}
2477 qtag=`sqlquote "$tag"`
2478 cond2="tag=$qtag"
2479 fi
2480 if [ -n "$cond1$cond2" ]; then
2481 cond="$cond1${cond2:+ AND $cond2}"
2482 cond="WHERE ${cond# AND }"
2483 fi
2485 # XX: これ複雑すぎるかな。もっとシンプルにしたい。$3条件も。2015-07-08
2486 # grpは呼出し元の動的スコープ変数でよくないな...
2487 ##qgrp=`sqlquote $grp`
2488 getgrp="(select gname from grp where rowid=${rowid:--1})"
2489 sql="select a.rowid, a.$link,
2490 coalesce(b.$gcs, a.$nm) as nick,
2491 quote(a.$nm) as qname,
2492 (SELECT val FROM ${tbl}_s
2493 WHERE $nm=a.$nm AND key='$iconcachekey') icon,
2494 coalesce(b.gecos, a.$nm) /* If group, concat (Nusers) */
2495 || case when a.$nm in (select gname from grp)
2496 then printf('(%d名)',
2497 (select count(user) from grp_mem where gname=a.$nm))
2498 else ' <'||a.$nm||'>'
2499 end
2500 as name,
2501 b.tag,
2502 case when a.$nm in (select user from grp_adm
2503 where gname=$getgrp) then '管理者'
2504 when '$user' in (select user from grp_adm where gname=a.$nm)
2505 then 'ADMIN'
2506 when '$user' in (select user from grp_mem where gname=a.$nm)
2507 then 'Member'
2508 when '$iamowner' = '' then ''
2509 else ',not='||a.rowid end as ownerlink,
2510 CASE '$entity'
2511 WHEN 'グループ'
2512 THEN coalesce(
2513 (SELECT val FROM grp_s WHERE gname=a.$nm AND key='regmode'),
2514 'open')
2515 ||
2516 CASE WHEN '$user'
2517 IN (SELECT user FROM grp_mem WHERE gname=a.$nm)
2518 THEN ' ismember'
2519 ELSE ''
2520 END
2521 ELSE 'user'
2522 END regmode
2523 from $tbl a left join
2524 (select $nm as name,
2525 max(case key when 'gecos' then val end) as gecos,
2526 max(case key when 'tag' then val end) as tag,
2527 max(case key when 'mtime' then val end) as mtime,
2528 max(case key when 'wtime' then val end) as wtime,
2529 max(case key when 'login' then val end) as login
2530 from ${tbl}_s group by $nm)
2531 b on a.$nm=b.name $cond $3
2532 order by b.wtime desc, b.login desc,
2533 b.mtime desc, b.tag desc, a.rowid asc"
2534 # Give precedence to newer maintained groups (2016-09-24)
2535 # Note that mtime is stored only in grp_s.
2536 ## err LE:sql.1="$sql"
2537 total=`query "with x as ($sql) select count(*) from x;"`
2538 echo "$enthead${entity} 一覧" "${S4WORLDNAME:+@$S4WORLDNAME}" | html h2
2539 echo '<div class="listentry">' # List-entry div
2540 # Show owner/member filter button
2541 METHOD=GET
2542 hiddens="$hiddens
2543 `cgi_hidden stage \"$stage\"`"
2544 if [ x"$tbl" = x"grp" ]; then
2545 args=`grep "^種別:" $formdir/grp.def | cut -d: -f5`
2546 fh="<select name=\"tag\">$nl"
2547 fh="$fh<option value=\"NULL\"${tag:+ selected}>グループ種別...</option>"
2548 for l in $args; do
2549 val=${l#*=} tname=${l%=*}
2550 if [ x"$val" = x"$tag" ]; then
2551 s=" selected"
2552 selectedtags="(種別[${tname}]のみ)"
2553 else
2554 s=""
2555 fi
2556 form=$nl$form"<option value=\"$val\"$s>$tname</option>"
2557 done
2558 form="$fh$form</select><input type=\"submit\" value=\"で絞る\">"
2559 cat<<-EOF
2560 <form action="$myname" method="$METHOD">
2561 </form>
2562 以下一覧のうち: `cgi_checkbox onlymem no 'id="ismembtn"'`<label
2563 for="ismembtn">参加中以外隠す</label>
2564 `cgi_checkbox onlyadm no 'id="isadmbtn"'`<label
2565 for="isadmbtn">管理者参加以外隠す</label>
2566 EOF
2567 # limit=3
2568 hiddens=$hiddens" "`cgi_hidden tag2 "$tag"`
2569 fi
2570 if [ $total -gt $limit -o \( -n "$S4WORLDLIST" -a x"$tbl" = x"grp" \) ]; then
2571 echo '<div>'
2572 METHOD=GET cgi_form $stage <<EOF
2573 $form
2574 <label>次の語を含む${entity}で検索:
2575 `cgi_text kwd "$kwd"`</label>
2576 $hiddens
2577 EOF
2578 echo '</div>'
2579 else
2580 echo $selectedtags | html p
2581 fi
2582 test -n "$kwd" && hiddens="$hiddens$nl`cgi_hidden kwd \"$kwd\"`"
2583 cat<<EOF
2584 <form action="$myname" method="$METHOD">
2585 <p>${total}件中の<input class="hidesub" type="text" name="start"
2586 value="$((offset+1))" size="3">件めから${kwd:+" - 検索語: $kwd"}$hiddens
2587 <input type="submit" value="確定"></p>
2588 </form>
2589 EOF
2590 if [ $((offset+limit)) -lt $total ]; then
2591 nextbtn=$(
2592 cat<<EOF
2593 <div class="right clear"><form action="$myname" method="$METHOD">
2594 `cgi_submit 次の${limit}件`
2595 $hiddens
2596 `cgi_hidden offset $((offset + limit))`</form></div>
2597 EOF
2599 fi
2600 if [ $offset -gt 0 ]; then
2601 prevbtn=$(
2602 cat<<EOF
2603 <form action="$myname" method="$METHOD">
2604 `cgi_submit 前の${limit}件`
2605 $hiddens
2606 `cgi_hidden offset $((offset - limit))`</form>
2607 EOF
2609 fi
2610 pnbtn="$nextbtn$prevbtn"
2611 echo $pnbtn
2613 ## err ListEntry: `echo "$sql"\;`
2614 # sq $db here??? 2016-11-28
2615 query "$sql limit $limit ${offset:+offset $offset};" \
2616 | while IFS='|' read id lnk name qname icon gecos tag ownerp type; do
2617 # err name=$name owner=$ownerp lnk=$lnk
2618 # err newlnk=$lnk regmode=$regmode
2619 icondir=$dir/$id
2620 # Pick up only last icon
2621 htmlname=`echo $name|htmlescape`
2622 echo "<div class=\"iconlist xy$thumbxy $type $ownerp\">
2623 <p class=\"tag _$tag\">$tag</p>" \
2624 | _m4 $tagconv
2625 if [ -n "$NOSPEEDUP" ]; then
2626 files=`getvalbyid $tbl profimg $id $icondir`
2627 if [ -n "$files" ]; then
2628 icon=`echo "$files"|tail -1`
2629 iconhref2 "$icondir/$icon" "$hrb+$lnk" "$gecos"
2630 else
2631 iconhref "$dir/$deficon" "$hrb+$lnk" "$gecos"
2632 fi
2633 elif [ -n "$icon" -a -s "$icon" ]; then
2634 iconhref2 "$icon" "$hrb+$lnk" "$gecos"
2635 else
2636 cond="$nm=$qname"
2637 # err imgsrc_cache "$dir/list" ${tbl}_m "$cond" S
2638 # err query "SELECT type FROM ${tbl}_m $cond LIMIT 1;"
2639 img=`query "SELECT type FROM ${tbl}_m WHERE $cond AND key='profimg' LIMIT 1;"`
2640 # err "img=[$img]"
2641 if [ -n "$img" ]; then
2642 echo "<a href=\"$hrb+$lnk\">"
2643 imgsrc_cache "$icondir" ${tbl}_m "$nm=$qname" S
2644 echo "</a>"
2645 else
2646 iconhref2 "$dir/$deficon" "$hrb+$lnk" "$gecos"
2647 fi
2648 fi
2649 echo "<br>$htmlname${ownerp:+<br>($ownerp)}"
2650 echo "</div>"
2651 done
2652 echo "</div>" # End of List-entry div
2653 echo ${pnbtn:+"<hr>$nextbtn$prevbtn"}
2654 if [ -n "$kwd" -a x"$tbl" = x"grp" -a -n "$S4WORLDS" ]; then
2655 peekgrpworlds "$kwd"
2656 fi
2658 listmember() {
2659 listentry user "$@"
2661 listgroup() {
2662 listentry group "$@"
2664 hexteams() { # $1=gname, $2(optional)=user
2665 cond=${2:+" AND user='$2'"}
2666 query "SELECT DISTINCT hex(val) FROM grp_mem_m
2667 WHERE gname='$1' AND key='team'$cond ORDER by val;"
2669 showgroup() { # $1=group-rowid
2670 if [ -z "$1" ]; then
2671 grid=`getpar grid`
2672 grid=${grid%%[!0-9]*}
2673 [ -n "$grid" ] && grp=`getgroupbyid $grid`
2674 else
2675 grid=$1
2676 fi
2677 grp=`getgroupbyid $grid`
2678 qgrp=`sqlquote "$grp"`
2679 htmlgrp=`echo "$grp"|htmlescape`
2680 ## err showgroup2: grid=$grid grp=$grp qgrp="[$qgrp]"
2681 if isgroup "$grp"; then
2682 tf=$tmpd/title.$$
2683 sf=$tmpd/search.$$
2684 bodyclass=`query "SELECT val FROM grp_s
2685 WHERE gname=$qgrp AND key='regmode';"`
2686 if ismember "$user" "$grp"; then
2687 ismember="ismember"
2688 bodyclass="$bodyclass${bodyclass:+ }ismember"
2689 else
2690 ismember="" # bodyclass="group"
2691 fi
2692 bodyclass="$bodyclass grouphome"
2693 echo "<div class=\"search\">`search_form grid=\"$grid\"`</div>"> $sf
2694 echo "グループ $htmlgrp" > $tf
2696 showgroupsub $formdir/grp.def "$grid" | \
2697 _m4 -D_TITLE_="syscmd(\`cat $tf')" \
2698 -D_FORM_="syscmd(\`cat')" \
2699 -D_BODYCLASS_="$bodyclass" \
2700 -D_DUMPTABLE_="" \
2701 $htmlheader $sf $layout/form+dump.m4.html
2702 # $htmlheader $layout/form+dump.m4.html
2703 # $htmlheader is defined in grp()
2704 else # if $grp is removed at par2table
2705 listgroup
2706 fi
2708 showgroupsub() {
2709 # $1=def-file $2=group-rowid
2710 # Using $ismember
2711 rowid=$2
2712 grp=`getgroupbyid $2`
2713 qgrp=`sqlquote "$grp"`
2714 td=`getcachedir grp/"$2"`
2715 #rowid=`sq $db "select rowid from grp where gname=$qgrp"`
2716 if [ -z "$rowid" ]; then
2717 #rowid=`sq $db "select rowid from grp where rowid=$grp"`
2718 #grp=`sq $db "select gname from grp where rowid=$grp"`
2719 echo "showgroupsub: invalid argument($1 $2)" | html p
2720 return
2721 fi
2722 val=`getvalbyid grp profimg $rowid $tmpd`
2723 enticond="gname=$qgrp"
2724 img=`query "SELECT type FROM grp_m WHERE $enticond LIMIT 1;"`
2725 if [ -n "$img" ]; then
2726 cat<<-EOF
2727 <p class="groupimg">
2728 `imgsrc_cache $td/main grp_m "$enticond" M`</p>
2729 EOF
2730 fi
2731 echo "<div class=\"noprofimg\">"
2732 viewtable $1 grp $rowid
2733 echo "</div>"
2734 if isgrpowner "$user" "$grp"; then
2735 echo "<p><a href=\"?groupconf+$rowid\" accesskey=\"e\"
2736 title=\"Shortcut: e${nl}Edit Group\">グループ情報の編集</a>"
2737 iamowner=$rowid
2738 colmd=" mode"
2739 fi
2740 if [ -n "$ismember" ]; then
2741 #echo "${iamowner:+ / }<a href=\"?blog+$rowid\">グループの新規話題作成</a>"
2742 #echo "/ <a href=\"?grpaction+$rowid\">メンバーを個別選択しての操作</a></p>"
2743 # div.fold input[type="checkbox"]:checked ~ div {display: block;}
2744 cat<<-EOF
2745 ${iamowner:+ / }<a accesskey="n" title="Shortcut: n${nl}New blog"
2746 href="?blog+$rowid">グループの新規話題作成</a>
2747 / <a accesskey="m" title="Shortcut: m${nl}Operations on Members"
2748 href="?grpaction+$rowid">メンバーを個別選択しての操作</a></p>
2749 <form action="?send2mem" method="POST" enctype="multipart/form-data">
2750 <div class="fold clear">
2751 `cgi_checkbox send yes id="send"`<label
2752 for="send">グループ全員にメッセージ送信</label>
2753 <div>
2754 `cgi_textarea message "" "cols=60"`
2755 `cgi_submit 送信`
2756 `cgi_reset リセット`
2757 </div>
2758 `cgi_hidden grp $rowid`
2759 </div></form>
2760 EOF
2761 fi
2762 # 加入ボタン + 加入者リスト
2763 if [ -n "$ismember" ]; then
2764 ismem='checked' state="(参加中)"
2765 else
2766 nomem='checked' state="(現在非加入)"
2767 fi
2768 # このグループでの加入アドレス
2769 eml=`query "select val from grp_mem_s where gname=$qgrp and user='$user' \
2770 and key='email';"`
2771 ##err EML: "select val from grp_mem_s where gname='$2' and user='$user' \
2772 ## and key='email';"
2773 ##err email=$eml
2774 cat <<EOF
2775 <div class="fold clear">
2776 `cgi_checkbox reg yes id="reg"`<label
2777 for="reg">自身の加入状態を操作する</label>$state
2778 <div>
2779 EOF
2780 cgi_form grp <<EOF
2781 <p>このグループに</p>
2782 <table class="b">
2783 <tr><th>メンバーとして</th><td>
2784 <label>`cgi_radio joingrp "yes" $ismem`参加</label> /
2785 <label>`cgi_radio joingrp "no" $nomem`参加しない</label></td></tr>
2786 <tr><th>参加する場合のメイルアドレス<br>
2787 <small>(メインのアドレスとは違うものにする場合に記入<br>
2788 同じでよい場合は空欄に)</small></th>
2789 <td>`cgi_text email $eml`</td></tr>
2790 </table>
2791 `cgi_hidden grp $rowid`
2792 EOF
2793 if [ x`getgroupattr "$grp" regmode` = x'moderated' -a -z "$ismem" ]; then
2794 echo "moderated (承認加入の)グループなので実際に参加できるのは
2795 グループ管理者が承認操作をした後になります。" | html p 'class="warn"'
2796 fi
2797 echo '</div></div>'
2798 echo '<h2>話題一覧</h2>'
2799 thelp="1ヶ月分のまとめには上部検索窓に @month と入れてください。"
2800 cat<<-EOF
2801 <form class="summary" action="$myname" title="$thelp">
2802 `cgi_hidden owner "$grp"`
2803 `cgi_hidden kwd "@week"`
2804 `cgi_hidden stage searchart`
2805 `cgi_submit "一週間のまとめ"`
2806 </form>
2807 EOF
2808 cond="where a.id in (select id from blog_s where key='owner' and val=$qgrp) order by ctime desc"
2809 colstate="state:稼動状態:frozen=rowclass=凍結"
2810 DT_CHLD=article:blogid \
2811 DT_QOWNER="$qgrp" \
2812 DT_VIEW=replyblog dumptable html blog \
2813 "ctime title heading team notify:通知$colmd $colstate" "$cond"
2815 getgname="(select gname from grp where rowid=$rowid)"
2816 c="group by a.name having a.name in (select user from grp_mem where gname=$getgname)"
2817 cm="?commission+$rowid"
2818 thumbxy=50x50 listmember "`getpar kwd`" "$c" "$rowid" \
2819 |sed -e "s|\(<br>\)(,not=\(.*\))|\1|" # 間違って押しやすい
2820 # team list
2821 hexteams=`hexteams "$grp"`
2822 if [ -n "$hexteams" ]; then
2823 echo "チーム一覧" | html h2
2824 echo '<div class="dumptable"><table class="b">'
2825 sq $db -html -header<<-EOF
2826 SELECT val TEAM,
2827 group_concat((SELECT gecos FROM gecoses WHERE name=user), ',')
2828 MEMBERS
2829 FROM grp_mem_m WHERE gname=$qgrp AND key='team' GROUP BY val;
2830 EOF
2831 echo '</table></div>'
2832 fi
2834 grp_getbodyclass() {
2835 # Get css class name for document.
2836 # `moderated' for moderated groups
2837 # `ismember' for groups where user belongs
2838 # $1=GroupName (w/o quote)
2839 # $user=userNameCurrentlyLogin
2840 ## err grp_getbodyclass: 1="$1"
2841 qgrp=`sqlquote "$1"`
2842 query<<-EOF
2843 SELECT coalesce(
2844 (SELECT val FROM grp_s WHERE gname=$qgrp AND key='regmode'),
2845 'open')
2846 ||
2847 CASE WHEN '$user'
2848 IN (SELECT user FROM grp_mem WHERE gname=$qgrp)
2849 THEN ' ismember'
2850 ELSE ''
2851 END;
2852 EOF
2854 grpaction() { # $1=group-rowid
2855 err GRP_ACTION:IN
2856 grid=${1:-`getpar grp`}
2857 grp=`getgroupbyid "$grid"`
2858 htmlgrp=`echo "$grp" | htmlescape`
2859 myuid=`query "SELECT rowid FROM user WHERE name='$user';"`
2860 if [ -z "$grp" ]; then
2861 echo "無効な指定です。" | html p; return
2862 fi
2863 if ! ismember $user "$grp"; then
2864 echo "加入者のみに許可された操作です。" | html p; return
2865 fi
2866 echo "グループ $grp 個別選択操作" \
2867 | _m4 -D_TITLE_="syscmd(\`cat')" \
2868 -D_BODYCLASS_="`grp_getbodyclass \"$grp\"`" \
2869 $layout/html.m4.html
2871 isowner=""
2872 isgrpowner "$user" "$grp" && isowner="yes"
2873 usel=`getpar usel`
2874 if [ -n "$usel" ]; then
2875 uids=$(echo `echo $usel`|tr ' ' ',')
2876 ## err grpaction-1: grp=$grp, `echo $sql`
2877 text=`getpar text|tr -d '\r'`
2879 rm=`getpar rm` cfm=`getpar confirm`
2880 ## err rm=$rm cfm=$cfm
2881 if [ x"$rm" = x"yes" ]; then
2882 if [ "$isowner" ]; then
2883 if [ x"$rm$cfm" = x"yesyes" ]; then
2884 # Eliminate
2885 cond="where gname=(select gname from grp where rowid=$grid) and user in (select name from user where rowid in ($uids))"
2886 for tbl in grp_mem grp_mem_s grp_mem_m; do
2887 sql="delete from $tbl $cond;"
2888 # echo "sql=$sql"
2889 query "$sql"
2890 err rmGRPuser "$sql"
2891 done
2892 num=`query "select count(*) from user where rowid in ($uids);"`
2893 #err num=$num
2894 if [ 0$num -gt 0 ]; then
2895 sql="select coalesce(b.val,a.name) from user a left join \
2896 user_s b on a.name=b.name and key='gecos' where a.rowid in ($uids);"
2897 # err `echo "$sql"`
2898 html pre<<EOF
2899 以下の${num}名のグループ $grp 登録を解除しました。
2900 `query "$sql"`
2901 EOF
2902 fi
2903 else
2904 echo "確認のチェックがないのでやめておきます。" | html p
2905 return
2906 fi
2907 else # not Group Owner
2908 echo "グループ管理者でないのでメンバー操作はできません。" | html p
2909 return
2910 fi
2911 cat<<EOF
2913 EOF
2914 elif [ x"$rm" = x"send" ]; then # if sendmsg mode
2915 if [ -z "$text" ]; then # if msg is empty
2916 echo "なにかメッセージを..." | html p
2917 return 0
2918 fi
2919 gecos=`gecos $user`
2920 safegc=`echo "$gecos" | tr -d '<>@'`
2921 #fromad=`email4groupbyuid "$grp" "$myuid" | sed -e 1q -e 's/[ ,].*//'`
2922 fromad=`myemail4group "$grp"`
2923 ###mail_from="$safegc <$fromad>"
2924 mail_from="$safegc <$user>" # TEST: 2020/5/13
2925 test -n `getpar sender` &&
2926 export SENDER=$user # TEST: 2020/5/15
2927 replyto=$fromad
2929 ## Start parse of attachment files
2930 if [ -n "`getpar email`" ]; then
2931 ar=`getpar supprcpt`
2932 if [ -n "$ar" ]; then
2933 for a in $ar; do
2934 if checkdomain "$a"; then
2935 supprcpt="$supprcpt $a"
2936 else
2937 err "SupprcptErr=[$a] by $user"
2938 removercpt="$removercpt $a"
2939 fi
2940 done
2941 fi
2942 subj=`getpar subject`
2943 afiles=""
2944 for fn in `query "SELECT DISTINCT val FROM par WHERE var='files' AND sessid='$session';"`
2945 do
2946 f=$tmpd/$fn
2947 if [ -s $f ]; then
2948 afiles=$afiles"${afiles:+ }$f"
2949 fi
2950 done
2951 else
2952 preface=$(cat <<-EOF
2953 $url
2954 のグループ「$grp」のメンバーである $gecos さんから、
2955 あなた宛のメッセージです。
2956 ----------------------------------------------------------
2957 EOF
2959 fi
2960 rcpts="`email4groupbyuid "$grp" $usel` $fromad$supprcpt"
2961 rcpts=`echo $rcpts|tr ' ' '\n'|sort|uniq|tr '\n' ' '`
2962 subj="${subj:-$gecos さんからのメッセージ}"
2963 REPLYTO=$replyto
2964 MAIL_FROM=$mail_from
2965 export REPLYTO SMAIL_TO MAIL_FROM
2966 err "GrpActionSend: user=[$user], MAIL_FROM=[$mail_from], rcpts=[$rcpts], REPLYTO=[$replyto}"
2967 for r in $rcpts; do
2968 if [ x"$user" = x"$r" -o x"$fromad" = x"$r" ]; then
2969 SMAIL_TO=$rcpts # Show all rcpts to sender oneself
2970 else
2971 # Show sender and rcpts address for guest
2972 SMAIL_TO=`echo $r $user $fromad|tr ' ' '\n'|sort -u|tr '\n' ' '`
2973 fi
2974 if [ -n "$afiles" ];then
2975 ./sendmultipart.sh -t "$r" -s "$subj" -f "$mail_from" $afiles
2976 else
2977 smail "$r" "$subj"
2978 fi <<EOF
2979 ${preface:+$preface$nl}$text
2980 EOF
2981 done
2982 if [ $? = 0 ]; then
2983 echo "Note: 以下のメンバーにメッセージを送信しました。" | html p
2984 sql="select coalesce(b.val, a.name) from
2985 (select name from user where rowid in ($uids)) a
2986 left join user_s b on a.name=b.name and b.key='gecos';"
2987 html pre<<EOF
2988 `query "$sql"`
2989 ${supprcpt:+追加宛先 $supprcpt$nl}(送信者である $gecos さんも含まれます)
2990 ${removercpt:+アドレスエラーによる削除(送られません): <em class="warn">$removercpt</em>}
2991 EOF
2992 err SendDone: `echo $sql`
2993 fi
2994 elif [ x"$rm" = x"commission" ]; then
2995 grp_reg_adm $grid $usel
2996 elif [ x"$rm" = x"addteam" ]; then
2997 team=`getpar team|sed "s/'/''/g"` # for single quotation
2998 newteam=`echo $team|tr -d ,` # ..and strip spaces of both ends
2999 if [ x"$team" != x"$newteam" ]; then
3000 echo "チーム名に使えない文字を除去しました" | html p
3001 team=$newteam
3002 fi
3003 if [ -z "$team" -o x"$team" = x"なし" -o x"$team" = x"TEAM" ]; then
3004 cat<<-EOF | html p
3005 有効なチーム名を入力してください。
3006 カンマだけ、「なし」という名前は使えません。
3007 EOF
3008 echo "有効なチーム名を入力してください。" | html p
3009 else
3010 grp_add_team $grid "$team" $usel
3011 fi
3012 elif [ x"$rm" = x"rmteam" ]; then
3013 if [ x"yes" = x"`getpar teamconfirm`" ]; then
3014 rmteam=`getpar rmteam|sed "s/'/''/g"`
3015 if [ -n "`query \"SELECT val FROM grp_mem_m WHERE\
3016 gname='$grp' AND user='$user' AND key='team'\
3017 AND val='$rmteam';\"`" ]; then
3018 grp_rm_team $grid "$rmteam" $usel
3019 else
3020 echo "所属していないチームの除去操作はできません。"|html p
3021 fi
3022 else
3023 echo "確認チェックなしなのでチーム除去しませんでした。"|html p
3024 fi
3025 fi
3026 fi
3027 # POST count summary
3028 from=`getpar from`; to=`getpar to`
3029 from_input="<input type=\"date\" name=\"from\" placeholder=\"YYYY-MM-DD\" value=\"${from}\">"
3030 to_input="<input type=\"date\" name=\"to\" value=\"${to:-9999}\">"
3031 fromtonote="<p title=\"Count the Number of Posts from-to\">POST集計: $from_input - $to_input</p><!-- $from - $to -->"
3032 # New entry
3033 sql="WITH mems AS (
3034 SELECT g.rowid, name, gecos FROM grp_mem gm LEFT JOIN gecoses g
3035 ON gm.user=g.name
3036 WHERE gname=(SELECT gname FROM grp WHERE rowid=$grid)
3037 ), target_article AS (
3038 SELECT id FROM article_s
3039 WHERE key='ctime' AND val BETWEEN '${from:-0000}' AND '${to:-9999}'
3040 ), posts AS (
3041 SELECT author, count(author) post
3042 FROM article NATURAL JOIN article_s NATURAL JOIN target_article
3043 WHERE blogid IN (SELECT id FROM blog_s
3044 WHERE key='owner'
3045 AND val=(SELECT gname FROM grp WHERE rowid=$grid))
3046 AND key='text'
3047 GROUP BY author
3048 ), teams AS (
3049 SELECT user, group_concat(val, ', ') team
3050 FROM grp_mem_m
3051 WHERE gname=(SELECT gname FROM grp WHERE rowid=$grid)
3052 AND key='team'
3053 GROUP BY user
3054 ), user_post AS (
3055 SELECT m.rowid, name, m.gecos, coalesce(post, 0) as POST
3056 FROM mems m LEFT JOIN posts
3057 ON m.name=posts.author
3058 GROUP by m.rowid
3060 SELECT
3061 CASE
3062 WHEN (SELECT user FROM grp_adm
3063 WHERE gname=(SELECT gname FROM grp WHERE rowid=$grid)
3064 AND user=up.name) IS NOT NULL
3065 then 'k'
3066 ELSE ''
3067 END || rowid || ',' || gecos NAME,
3068 post POST, team _TEAM_
3069 FROM user_post up LEFT JOIN teams t
3070 ON up.name=t.user
3071 ORDER BY gecos;"
3072 ## err grpaction: "`echo \"$sql\"`"
3073 tf=$tmpd/title.$$
3074 echo "グループ[<a href=\"?grp+$grid\">$htmlgrp</a>]参加メンバーに対する操作" > $tf
3075 cmmsg="`cgi_radio rm commission id=\"cmadmin\"`<label accesskey=\"f\"
3076 title=\"Shortcut: f${nl}Add to Administrator of the Group\"
3077 for=\"cmadmin\">管理者委任</label>
3078 <div><p>このグループでの全権を付与します。信頼できる人に託してください。
3079 </p></div>"
3080 excmsg="`cgi_radio rm yes id=\"conf\"`<label accesskey=\"g\"
3081 title=\"Shortcut: g${nl}Remove from the Group\"
3082 for=\"conf\">GRP登録解除</label>
3083 <div>本当に消します! `cgi_checkbox confirm yes` 確認
3084 <p>この操作による通知は本人に行きません。
3085 あらかじめ通知するか、登録解除してよい状況かしっかり確認してください。</p>
3086 </div>"
3087 # Get team list to which current user belongs into $hexteams
3088 allhexteams=$(hexteams "$grp")
3089 if [ -n "$isowner" ]; then
3090 myhexteams="$allhexteams" # admin can remove all teams' attr
3091 else
3092 myhexteams=$(hexteams "$grp" "$user")
3093 fi
3094 if [ -n "$myhexteams" ]; then
3095 rmteammsg="`cgi_radio rm rmteam 'id=\"cmrmteam\"'`<label accesskey=\"s\"
3096 title=\"Shortcut: s${nl}Strip a team tag from\"
3097 for=\"cmrmteam\">チーム属性除去</label>
3098 <div>チーム属性:`cgi_select_h rmteam \"2d2d2d\" $myhexteams`
3099 を除去します: `cgi_checkbox teamconfirm yes` 確認
3100 <p>この操作による通知は本人に行きません。
3101 あらかじめ通知するか、登録解除してよい状況かしっかり確認してください。</p>
3102 </div><!-- end of $rmteammsg -->
3104 fi
3105 stf=$tmpd/selteam.$$
3106 cgi_select_h selteam "5445414d" $allhexteams > $stf
3107 b1='<label> <input type="checkbox" name="usel" value="'
3108 ba='<label class="admin"><input type="checkbox" name="usel" value="'
3109 br='<span id="reverse" title="Reverse Selection"></span>'
3110 #b2='"> <span>' b3='</span></label>'
3111 # | sed -e "s|^\(<TR><TD>\)k\([0-9]*\),\([^<]*\)|\1$ba\2$b2\3$b3|" \
3112 # -e "s|^\(<TR><TD>\)\([0-9]*\),\([^<]*\)|\1$b1\2$b2\3$b3|" \
3113 lnk='"> <span>\3</span></label> [<a href="?home+\2">HOME</a>]'
3114 cgi_form grpaction<<EOF \
3115 | sed -e "s|^\(<TR><TD>\)k\([0-9]*\),\([^<]*\)|\1$ba\2$lnk|" \
3116 -e "s|^\(<TR><TD>\)\([0-9]*\),\([^<]*\)|\1$b1\2$lnk|" \
3117 -e "s|^\(<TR><TH>\)\(NAME\)|\1$br \2|" \
3118 | _m4 -D_TITLE_="spaste(\`$tf')" \
3119 -D_SUBTITLE_="チェック後操作ボタン" \
3120 -D_FORM_="syscmd(cat)" -D_DUMPTABLE_="" \
3121 $layout/form+dump.m4.html \
3122 | _m4 -D_TEAM_="spaste(\`$stf')"
3123 <p>下でチェックした人を対象として:</p>
3124 <div class="foldtabs">
3125 `cgi_radio rm addteam 'id="cmteam"'`<label accesskey="a"
3126 title="Shortcut: a${nl}Add a team tag to"
3127 for="cmteam">同じチーム属性を付与</label>
3128 <div>チーム名:`cgi_text team "" 'id="inteam" list="teams"'`
3129 `cgi_datalist_h teams $allhexteams`
3130 </div>
3131 ${rmteammsg}
3132 `cgi_radio rm send id="sendmsg"`<label accesskey="d"
3133 title="Shortcut: d${nl}DirectMail to"
3134 for="sendmsg" title="Direct Message">DM送信</label>
3135 <div>
3136 `cgi_checkbox email yes 'id="email" class="fold"'`<label for="email"
3137 title="Using email format">email書式を使う</label>
3138 <div class="folded">
3139 <table>
3140 <tr><td>From: </td><td>$user</td></tr>
3141 <tr><td>このFrom:で送る</td>
3142 <td>`cgi_checkbox sender yes 'checked'`<small></small>
3143 </td></tr>
3144 <tr><td>Subject: </td><td>`cgi_text subject`</td></tr>
3145 <tr><td>追加宛先(通常空欄): </td><td>`cgi_text supprcpt ""`</td></tr>
3146 <tr><td>ファイル添付: </td>
3147 <td>`cgi_file files "" "multiple $file_accept title=\"$file_accept_help\""`<br><small>文書ファイルはPDFに変換してから</small></td></tr>
3148 </table>
3149 <p>(下記一覧から1人以上選択していない場合は送れません`cgi_submit 確認後送信`)</p>
3150 </div>
3151 <div>本文:`cgi_textarea text "" cols=72`
3152 </div>
3153 </div>
3154 ${isowner:+$cmmsg$excmsg}
3155 `cgi_radio rm close id="x"`<label for="x" accesskey="x">×</label>
3156 </div>
3157 <h4>$htmlgrp 参加者一覧</h4>$fromtonote
3158 <table class="td2r thl">
3159 `sq $db -header -html "$sql"`
3160 </table>
3161 `cgi_hidden grp $grid`
3162 `cgi_hidden myuid $myuid id="myuid"`
3163 EOF
3165 crview4article() { # $1=rowid of blog, $2(optional)=extra SQL
3166 # Create TEMPORARY VIEW
3167 query<<EOF
3168 CREATE TEMPORARY VIEW writeusers AS
3169 SELECT DISTINCT author FROM article
3170 WHERE id in (
3171 select id from article where blogid=(select id from blog where rowid=$1)
3172 );
3173 CREATE TEMPORARY VIEW movablegroups AS
3174 SELECT g.rowid growid , g.gname
3175 FROM (SELECT grp.rowid, grp.gname FROM grp JOIN grp_mem gm
3176 ON grp.gname=gm.gname -- そのユーザが属している
3177 AND user='$user') g -- グループに絞る
3178 WHERE (SELECT author FROM writeusers
3179 EXCEPT
3180 SELECT user FROM grp_mem gm WHERE gm.gname = g.gname)
3181 IS NULL;
3182 $2
3183 EOF
3185 sql4readableblogs() {
3186 # Create view of blogs that can be readable to $user
3187 # Blog is readable when:
3188 # 1: blog owner is an user
3189 # 2: else, 2.1: owner-group where the $user belongs
3190 # 2.2: else, owner-group is not moderated
3191 # blog(id, author), blog_s(id, key='owner', val= ->owner)
3192 cat<<EOF ## | tee tmp/sql.out
3193 CREATE TEMPORARY VIEW readableblogs AS
3194 SELECT blog.rowid rid, id, author
3195 FROM blog
3196 NATURAL JOIN
3197 (SELECT id,
3198 max(CASE key WHEN 'owner' THEN val END) owner,
3199 max(CASE key WHEN 'mode' THEN val END) mode
3200 FROM blog_s GROUP by id) bs
3201 WHERE CASE WHEN (SELECT name FROM user where name=bs.owner) IS NOT NULL
3202 THEN 1 -- blog owner is an user, READABLE
3203 WHEN (SELECT val FROM grp_s
3204 WHERE gname=bs.owner AND key='regmode') = 'moderated'
3205 AND
3206 (SELECT user FROM grp_mem
3207 WHERE gname=bs.owner AND user='$user') IS NULL
3208 THEN 0
3209 WHEN mode IN ('quiz', 'enquete')
3210 THEN 0 -- "quiz" mode blog cannot be searched
3211 ELSE 1
3212 END;
3213 EOF
3215 editheading() { # $1=rowid-of-heading
3216 rowid=${1%%[!A-Z0-9a-z_]*}
3217 if [ -z "$rowid" ]; then
3218 echo "話題番号が未指定です。" | html p
3219 return
3220 fi
3221 owner=`getvalbyid blog owner $rowid`
3222 title=`getvalbyid blog title $rowid`
3223 GF_ACTION="?blog" edittable $formdir/blog.def blog $rowid \
3224 | _m4 -D_TITLE_="修正" \
3225 -D_SUBTITLE_="[$title]@$owner" -D_DIARY_="" \
3226 -D_BLOGS_="" -D_DUMPTABLE_="" \
3227 -D_FORM_="syscmd(\`cat')" \
3228 $layout/html.m4.html $layout/form+dump.m4.html
3229 # Move to group
3230 if isuser "$owner"; then
3231 crview4article $rowid
3232 n=`query "SELECT count(*) FROM writeusers;"`
3233 ## err N=$n
3234 if [ $((n)) -gt 0 ]; then
3235 ## err ROWID=$rowid
3236 sql="SELECT growid || ':' || gname FROM movablegroups;"
3237 cat<<-EOF
3238 <div class="fold">
3239 `cgi_checkbox mv send id="mv"`<label
3240 for="mv">この話題をグループ所有に移動する</label>
3241 <div>
3242 <form action="?mvart" method="POST" enctype="multipart/form-data">
3243 移動先グループ:
3244 <select name="mv2grp">
3245 EOF
3246 query ".mode html"
3247 query<<-EOF |
3248 $sql
3249 .mode list
3250 EOF
3251 sed -e '/<\/TR>/d' -e 's,<TR>,,' -e 's,TD>,option>,g' \
3252 -e 's,n>\([0-9]*\):\(.*\)<,n value="\1">\2<,'
3253 cat<<-EOF
3254 </select>
3255 <p>(移動できるグループは、この「話題」に書き込んでいる人全てが
3256 そのグループに加入しているものに限られます)</p>
3257 <p>`cgi_checkbox cfm yes`<label>確認
3258 (この操作は元に戻すことができません)</label></p>
3259 `cgi_hidden blogrowid $rowid`
3260 `cgi_submit 移動`
3261 `cgi_reset Reset`
3262 </form>
3263 </div>
3264 </div>
3265 EOF
3266 fi
3267 # end of isuser "$owner"
3268 elif { hexteams=$(hexteams "$owner" ) # blog is of GROUP
3269 [ -n "$hexteams" ];}; then
3270 none="`echo なし|hexize`"
3271 cat<<-EOF
3272 <div class="fold">
3273 `cgi_checkbox mv2team send id="mv2team"`<label
3274 for="mv2team">この話題を以下のチームのものにする</label>
3275 <div><p>現在の所属チーム設定:
3276 `query "SELECT
3277 coalesce((SELECT val FROM blog_s
3278 WHERE id=(SELECT id FROM blog WHERE rowid=$rowid)
3279 AND key='team'),
3280 ':なし');"`</p>
3281 <form action="?mvart" method="POST" enctype="multipart/form-data">
3282 移動先チーム: `cgi_select_h mv2team $none $hexteams`
3283 <p>`cgi_checkbox cfm yes`<label>確認</label></p>
3284 `cgi_hidden blogrowid $rowid`<br>
3285 `cgi_submit 移動`
3286 `cgi_reset Reset`
3287 </form></div></div>
3288 EOF
3289 fi
3291 mvart() { # move diary to some group or team
3292 # or move blog of group to team which belong to the group
3293 blogrowid=`getpar blogrowid`
3294 cfm=`getpar cfm`
3295 ##### echo move blog:$blogrowid to $mv2grp | html p
3296 blogrowid=${blogrowid%%[!A-Z0-9a-z_]*} # Purify
3297 . ./s4-blog.sh
3298 if [ -z "$blogrowid" ]; then
3299 echo "無効な指定です(mvart)。" | html p
3300 return
3301 elif [ x"$cfm" != x"yes" ]; then
3302 echo "記事移動の確認にチェックがないので通常表示に戻ります。" | html p
3303 elif { mv2grp=`getpar mv2grp`
3304 mv2grp=${mv2grp%%[!A-Z0-9a-z_]*} # Purify
3305 [ -n "$mv2grp" ]; }; then
3306 crview4article $blogrowid
3307 ########## TRANSACTION BEGIN
3308 query "BEGIN;"
3309 n=`query "SELECT count(*) FROM writeusers;"`
3310 ## err Nwriteuser=$n
3311 if [ $((n)) -gt 0 ]; then
3312 query<<-EOF
3313 UPDATE blog_s SET val=(SELECT gname FROM grp WHERE rowid=$mv2grp)
3314 WHERE key='owner'
3315 AND id=(SELECT id FROM blog WHERE rowid=$blogrowid)
3316 AND $mv2grp IN (SELECT growid FROM movablegroups);
3317 EOF
3318 fi
3319 query "END;"
3320 ########## TRANSACTION END
3321 elif { mv2team=`getpar mv2team|sed "s/'/''/g"`
3322 [ -n "$mv2team" ];}; then
3323 # blog owner can move it to ANY team
3324 case "$mv2team" in
3325 'なし')
3326 cat<<-EOF
3327 DELETE FROM blog_s
3328 WHERE id=(SELECT id FROM blog WHERE rowid=$blogrowid)
3329 AND key='team';
3330 EOF
3331 ;;
3332 "") ;;
3333 *)cat<<-EOF
3334 BEGIN;
3335 REPLACE INTO blog_s(id, key, val)
3336 VALUES((SELECT id FROM blog WHERE rowid=$blogrowid),
3337 'team', '$mv2team');
3338 REPLACE INTO blog_s(id, key, val)
3339 VALUES((SELECT id FROM blog WHERE rowid=$blogrowid),
3340 'notify', 'all'); -- Change notify to all
3341 END;
3342 EOF
3343 esac | query
3344 fi
3345 blog_reply $blogrowid
3346 echo yes | html p
3348 editart() { # $1=article-rowid $2=blogrowid
3349 rowid=${1%%[!A-Z0-9a-z_]*}
3350 blogrowid=${2%%[!A-Z0-9a-z_]*}
3351 if [ -z "$rowid" -o -z "$blogrowid" ]; then
3352 echo "表示する記事番号が未指定です。" | html p
3353 return
3354 fi
3355 owner=`getvalbyid blog owner $blogrowid`
3356 title=`getvalbyid blog title $blogrowid`
3357 author=`getvalbyid article author $rowid`
3358 ## err EDITart: owner=$owner, author=$author
3359 if isgrpowner "$user" "$owner"; then
3360 : EDIT OK
3361 elif [ x"$owner" != x"$user" -a x"$author" != x"$user" ]; then
3362 echo "本人か所有者しか編集できません." | html p
3363 return
3364 fi
3365 aid=`query "select id from article where rowid=$rowid;"`
3366 tmpout=$tmpd/editart.$$.out
3367 GF_ACTION="?replyblog+$blogrowid#$aid" \
3368 edittable $formdir/article.def article $rowid \
3369 > $tmpout
3370 rm -f /tmp/editart.out
3371 # Cannot use pipelining to m4 with genform() because of stdin stack
3372 _m4 -D_TITLE_="コメントの修正" -D_DIARY_="" \
3373 -D_FORM_="syscmd(cat $tmpout)" \
3374 -D_SUBTITLE_="`gecos $owner`の「$title」" \
3375 -D_BLOGS_= -D_DUMPTABLE_= \
3376 $layout/html.m4.html $layout/form+dump.m4.html
3378 send2mem() {
3379 rowid=`getpar grp`
3380 rowid=${rowid%%[!0-9]*} # Cleaning
3381 if [ -z "$rowid" ]; then
3382 echo "グループが未指定です。" | html p
3383 return
3384 fi
3385 message=`getpar message`
3386 if [ -z "$message" ]; then
3387 echo "文章を入れてください。" | html p
3388 return
3389 fi
3390 grp=`getgroupbyid $rowid`
3391 members=`collectemail "$grp"`
3392 myuid=`query "SELECT rowid FROM user WHERE name='$user';"`
3393 mailfrom=`email4groupbyuid "$grp" "$myuid" | sed -e 1q -e 's/[ ,].*//'`
3394 mailfrom="`gecos "$user"` <$mailfrom>"
3395 sj="グループ「$grp」宛メッセージ(from `gecos $user`)"
3396 msg=$(cat<<-EOF
3397 $urlbase?grp+$rowid
3398 グループ $grp に所属する
3399 `gecos $user` さんよりメッセージ:
3401 $message
3402 EOF
3404 # smail rcpt subj (file)
3405 for m in $members; do
3406 echo "$msg" |
3407 MAIL_FROM=$mailfrom \
3408 SENDER=$noreply \
3409 REPLYTO=$mailfrom \
3410 smail "$m" "$sj"
3411 done
3412 cat<<EOF
3413 <p>以下のユーザに送信しました。</p>
3414 <pre>
3415 `collectgecosesbyid "$rowid" | sed 's/$/ さん/'`
3416 </pre>
3417 <p><a href="?grp+$rowid">グループ $grp</a>に戻る。</p>
3418 EOF
3420 joingrpadmit() {
3421 # $1=yes/no $2=session-key
3422 if [ -z "$2" ]; then
3423 echo "bye bye" | html p; return
3424 fi
3425 t_usr=`session=$2 getpar adduser`
3426 t_grp=`session=$2 getpar group`
3427 ## err joingrpadmit: t_usr=$t_usr, t_grp=$t_grp
3428 _m4 -D_TITLE_="joingrp" $layout/html.m4.html
3429 if [ -z "$t_usr" -o -z "$t_grp" ]; then
3430 echo "無効な加入依頼です。" | html p
3431 echo "有効期限が切れたか、
3432 他の管理者がいる場合は処理済みの可能性があります。" | html p
3433 return
3434 fi
3435 if ! isgrpowner "$user" "$t_grp"; then
3436 echo "グループ管理者のみの機能です。" | html p; return
3437 fi
3438 case $1 in
3439 yes) joingrp "$t_grp" "$t_usr" yes ;;
3440 no) joingrp "$t_grp" "$t_usr" no ;;
3441 *)
3442 echo "無効な指定です($1)。" | html p
3443 return ;;
3444 esac
3445 gid=$(query "select rowid from grp where gname=`sqlquote \"$t_grp\"`;")
3446 rcpts="`getgroupadminmails "$t_grp"` $user"
3447 ## err admit: msgdir=$msgdir, rcpts="["$rcpts"]"
3448 body="に
3449 $t_usr
3450 `[ x$1 = xyes ] && echo 'を追加' || echo 'の解除操作を'`
3451 しました。"
3452 echo "$t_grp$nl$body$nl$nl$url?grp+$gid" | smail "$rcpts" "joingrp $1"
3453 query "delete from session where id='$2';"
3454 echo "グループ <a href=\"?grp+$gid\">$t_grp</a>$nl$body" | html p
3457 joingrprequest() {
3458 # $1=group $2=user $3=yes/no $4=email(if any $5=AsAdmin)
3459 jss="joingrp-`date +%s`-`genrandom 12`"
3460 addsession $jss +${memoplimitdays}days
3461 grpadmins=`getgroupadmins "$1"`
3462 query "replace into par values('$jss', 'group', 'string', `sqlquote \"$1\"`),
3463 ('$jss', 'adduser', 'string', `sqlquote \"$user\"`);"
3464 smail "$(collectemail $grpadmins)" "Join request to $1"<<EOF
3465 $url
3466 $user さんから
3467 グループ $1
3468 に加入依頼がありました。
3470 承認する:
3471 $urlbase?joingrpadmit+yes+$jss
3473 白紙に戻す:
3474 $urlbase?joingrpadmit+no+$jss
3475 EOF
3476 echo "管理者に加入依頼を出しました。
3477 ${memoplimitdays}日以内に加入承認操作がされれば加入できますが、
3478 グループ運用方針に懸かることですので直接の問い合わせが重要です。" | html p
3480 joingrp() {
3481 # $1=group $2=user $3=yes/no $4=email(if any $5=AsAdmin)
3482 ## err joingrp: \$1=$1 \$2=$2 \$3=$3 \$4=$4
3483 if isgrpowner "$user" "$1"; then
3484 isowner="yes"
3485 elif [ -n "$5" ]; then
3486 isowner="yes"
3487 else
3488 isowner=""
3489 fi
3490 ## err jg:isgrpowner: isowner="$isowner"
3491 if [ -n "$isowner" ]; then
3492 : # GROUP OWNER CAN DO EVERYTHING ABOUT REGISTRATION/RETIREMENT
3493 elif [ x"$2" != x"$user" ]; then # if user is not login user
3494 echo "本人か、グループ管理者しか加入操作はできません。" | html p
3495 return
3496 elif [ x"$3" = x"no" ]; then
3497 : # Do not pursue those who leave
3498 elif [ x"$3" = x"yes" ] && ismember "$user" "$grp"; then
3499 : # Member can change own email address for the joining moderated group
3500 else # adding user is $user itself
3501 case `getgroupattr "$1" regmode` in
3502 moderated)
3503 joingrprequest "$@" # Request only
3504 return
3505 ;;
3506 *)
3507 ;;
3508 esac
3509 fi
3510 qgname=`sqlquote "$1"`
3511 grid=`query "SELECT rowid FROM grp WHERE gname=$qgname;"`
3512 cond="where gname=$qgname and user='$2'"
3513 if [ x"$3" = x"yes" ]; then
3514 query "replace into grp_mem values($qgname, '$2');"
3515 # Notify joingrp to admin
3516 action="に加入しました。"
3517 if [ -n "$4" ]; then
3518 if msg=`emaildomaincheck "$4"`; then
3519 query "replace into grp_mem_s values($qgname, '$user', 'email', \
3520 'string', '$4', NULL);"
3521 else
3522 echo $msg
3523 fi
3524 else
3525 query "delete from grp_mem_s $cond and key='email';"
3526 fi
3527 if [ -n "$5" ]; then # as ADMIN
3528 # Coming here means newly created group
3529 sql="select case\
3530 when (select count(*) from grp_mem where gname=$qgname)=1\
3531 then (select user from grp_mem\
3532 where gname=$qgname and user='$user')\
3533 else '' end; "
3534 err NewGrpChk: $sql
3535 if [ -n "`query \"$sql\"`" ]; then
3536 ## err ADMIN: "replace into grp_adm values($qgname, '$user');"
3537 query "replace into grp_adm values($qgname, '$user');"
3538 fi
3539 fi
3540 else
3541 query "begin;
3542 delete from grp_mem $cond;
3543 delete from grp_mem_s $cond;
3544 delete from grp_mem_m $cond;
3545 delete from grp_adm $cond;
3546 delete from grp_adm_s $cond;
3547 delete from grp_adm_m $cond;
3548 end;"
3549 action="から脱退しました。"
3550 fi
3551 smail_queue "$(collectemail `getgroupadmins $1`)" "Member change of $1"<<-EOF
3552 $url?grp+$grid
3553 $user (`gecos $user`)さんが
3554 グループ $1
3555 $action
3556 EOF
3558 grp_add_team() (
3559 # $1=grp-rowid $2=team $3...=user-rowid(s)
3560 grp=`getgroupbyid $1`
3561 team=$2; shift; shift
3562 [ -z "$grid" -o -z "$team" -o -z "$1" ] && return
3563 { echo "BEGIN;"
3564 for user; do
3565 echo "REPLACE INTO grp_mem_m(gname, user, key, type, val) VALUES(\
3566 '$grp',\
3567 (SELECT name FROM user WHERE rowid=$user),\
3568 'team', 'string', '$team');"
3569 done
3570 echo "END;"
3571 } | query
3573 grp_rm_team() (
3574 # $1=grp-rowid $2=team $3...=user-rowid(s)
3575 grid=$1
3576 qgrp=$(sqlquote "`getgroupbyid $grid`")
3577 team=$2; shift; shift
3578 [ -z "$grid" -o -z "$team" ] && return
3579 { echo "BEGIN;"
3580 for user; do
3581 echo "DELETE FROM grp_mem_m\
3582 WHERE gname=$qgrp \
3583 AND user=(SELECT name FROM user WHERE rowid=$user)\
3584 AND key='team' AND val='$team';"
3585 done
3586 cat<<-EOF
3587 DELETE FROM blog_s
3588 WHERE rowid=(
3589 SELECT rowid
3590 FROM blog_s a
3591 WHERE key='team'
3592 AND id IN (SELECT id FROM blog_s WHERE key='owner' AND val=$qgrp)
3593 AND NOT EXISTS (SELECT * FROM grp_mem_m
3594 WHERE key='team' AND val=a.val -- a.val=team
3595 AND gname = (SELECT val FROM blog_s b
3596 WHERE a.id=b.id AND key='owner')
3597 ));
3598 EOF
3600 echo "END;"
3601 } | query
3603 grp_reg_adm() {
3604 # $1=grp-rowid $2...=user-rowid
3605 grid=$1
3606 grp=`getgroupbyid "$1"`
3607 if [ -z "$grp" ]; then
3608 echo "無効なグループIDです" | html p; return
3609 fi
3610 if ! isgrpowner "$user" "$grp"; then
3611 echo "$grp グループの管理者しかこの操作はできません。" | html p; return
3612 fi
3613 shift
3614 for urid; do
3615 newadm=`query "select name from user where rowid=$urid;"`
3616 if [ -z "$newadm" ]; then
3617 echo "指定ユーザIDがおかしいようです。" | html p; return
3618 fi
3619 err GRP_reg_adm: "replace into grp_adm values(`sqlquote \"$grp\"`, '$newadm');"
3620 err ismember $newadm $grp
3621 if ismember $newadm "$grp"; then
3622 # OK, go ahead
3623 getgname="(select gname from grp where rowid=$grid)"
3624 query "replace into grp_adm values($getgname, '$newadm');"
3625 # confirm insertion
3626 sql="select * from grp_adm where gname=$getgname and user='$newadm'"
3627 if [ -n "`query \"$sql;\"`" ]; then
3628 echo "追加完了: $newadm" | html p
3629 else
3630 echo "追加失敗($1 $urid)" | html p
3631 fi
3632 fi
3633 showgroup $grid
3634 done
3636 dt_rowhack() {
3637 # From: <TR>
3638 # ....
3639 # <TD>rowclass=foo</TD>
3640 # </TR>
3641 # To: <TR class="foo">....<TD>foo</TD></TR>
3642 sed -e '
3643 /^<TR>/ {
3644 :loop
3645 s/\n//
3647 /<\/TR>/ {
3648 s/\n//
3649 s,^<TR>\(.*\)<TD>rowclass=\(.*\)\(</TD></TR>\),<TR class="\2">\1<TD>\2\3,
3652 $q
3653 b loop
3654 }'
3656 dumptable() {
3657 # $1=mode $2=Table $3=column-list-of-*_s(defaults to *) $4=conditions(if any)
3658 # textのフィールドだけ全てダンプにしたほうがいいか
3659 # $DT_VIEW sets link
3660 # 6/17の次: editリンクじゃなくてスレッドVIEWリンクでいいんちゃう?
3661 ### elink="<a href=\"$myname?edittable+$2+\\2\">EDIT</a>"
3662 VIEW=${DT_VIEW-replyblog}
3663 if [ -n "$VIEW" ]; then
3664 dvlink=" <a href=\"$myname?$VIEW+\\2\\3\">VI</a><a href=\"$myname?$VIEW+\\2#bottom\">EW</a>"
3665 fi
3666 sqlfile=$tmpd/dump.sql
3667 : > $sqlfile # ensure to be empty
3668 printf '.mode html\n.header 1\n' > $sqlfile
3669 # $DT_CHLD=ChildTable:BindColumn
3670 if [ -n "$DT_CHLD" ]; then
3671 _t=${DT_CHLD%:*} _i=${DT_CHLD#*:}
3672 cat<<-EOF >> $sqlfile
3673 -- presql
3674 CREATE TEMPORARY TABLE IF NOT EXISTS myacclog AS
3675 SELECT * FROM acclog WHERE user='$user' and tbl='$2';
3676 EOF
3677 # Speed up counting of new articles
3678 cat<<-EOF >> $sqlfile
3679 -- presql2
3680 DROP TABLE IF EXISTS _counts;
3681 CREATE TEMPORARY TABLE _counts AS
3682 SELECT $_i, count($_i) cnt
3683 FROM $_t GROUP BY $_i;
3684 /* Prepare NEW count table */
3685 CREATE TEMPORARY TABLE _target AS
3686 SELECT b.rowid trowid, b.id
3687 FROM "$2" b JOIN "$2_s" s
3688 ON b.id=s.id AND s.key='owner'
3689 ${DT_QOWNER:+ AND s.val=$DT_QOWNER};
3691 DROP TABLE IF EXISTS _children;
3692 CREATE TEMPORARY TABLE _children AS
3693 SELECT a.trowid trowid, $_i, a.id, s.val ctime
3694 FROM (SELECT t.trowid, t.id $_i, a.id
3695 FROM _target t LEFT JOIN "$_t" a ON t.id=a.$_i) a
3696 LEFT JOIN ${_t}_s s ON a.id=s.id AND s.key='ctime';
3698 -- ${ddd:+.system gdate +%T.%3N >> tmp/d1}
3699 DROP TABLE IF EXISTS _news;
3700 DROP VIEW IF EXISTS _news;
3702 -- CREATE TEMPORARY TABLE _news($_i, newcnt);
3703 -- INSERT INTO _news
3704 /* **COMPARE** the efficiency of TEMP-TABLE and VIEW !!! */
3705 CREATE TEMPORARY VIEW _news AS
3706 SELECT a.id $_i, coalesce(newcnt, 0) newcnt
3707 FROM (SELECT DISTINCT id FROM _target)
3708 a LEFT JOIN
3709 (SELECT $_i, count(ctime) newcnt
3710 FROM _children x
3711 WHERE ctime > coalesce((SELECT time from myacclog
3712 WHERE tblrowid=x.trowid),
3713 '1970-01-01')
3714 GROUP BY $_i) b
3715 ON a.id=b.$_i;
3716 -- ${ddd:+.system gdate +%T.%3N >> tmp/d1}
3717 EOF
3718 # REMOVE next line until 2019/5/1
3719 cntall="(select count($_i) from $_t where $_i=a.id)"
3720 cntall="(coalesce((select cnt from _counts where $_i=a.id), 0))"
3721 # REMOVE next assignment until 2019/5/1
3722 cntnew="(select count(val) from ${_t}_s where key='ctime' \
3723 and id in (select id from $_t where $_i=a.id) \
3724 and val > coalesce((select time from myacclog where \
3725 tblrowid=a.rowid),\
3726 '1970-01-01'))"
3727 cntnew="(SELECT newcnt FROM _news where $_i=a.id)"
3728 cnt="$cntnew as '新着', $cntall as '総数',"
3729 dt_class=" td2r td3r dumpblogs"
3730 fi
3731 # Construct join expression
3732 eav="" scols=""
3733 pk=`gettblpkey $2`
3734 substr=${dumpcollen:+"substr(%s, 0, $dumpcollen)"}
3735 substr=${substr:-%s}
3736 for col in ${3:-`gettbl_s_cols $2`}; do
3737 valvar=val
3738 case $col in
3739 gecos) scols="$scols${scols:+, }${col#}"
3740 continue ;; # built-in column name
3741 *:*) as=${col#*:} # as can be 稼動状態:frozen=凍結中
3742 col=${col%%:*} # stage:稼動状態:frozen=凍結中 -> stage
3743 case "$as" in
3744 *:*=*) cnd=${as#*:}
3745 h=${cnd%%=*} v=${cnd#*=}
3746 h=`sqlquotestr "$h"`
3747 v=`sqlquotestr "$v"`
3748 valvar="CASE val WHEN $h THEN $v END"
3749 as=${as%%:*} ;;
3750 esac
3751 ;;
3752 *) as=${col} ;;
3753 esac
3754 ss=`printf "$substr" "$valvar"`
3755 eav=$eav${eav:+,}" max(case key when '$col' then $ss end) as $as"
3756 scols="$scols${scols:+, }b.$as"
3757 done
3758 #case author when '$user' then a.rowid else '---' end as ID,
3759 if [ -n "$DT_SQL" ]; then
3760 echo "$DT_SQL"
3761 else
3762 cat<<-EOF
3763 SELECT a.rowid as LINK, $cnt $scols
3764 FROM $2 a LEFT JOIN
3765 (SELECT $pk,$eav,
3766 max(CASE key
3767 WHEN 'owner'
3768 THEN (SELECT gecos FROM gecoses WHERE name=val) END)
3769 as gecos
3770 FROM ${2}_s c GROUP BY $pk)
3771 b ON a.$pk=b.$pk $4;
3772 EOF
3773 fi >> $sqlfile
3774 ## err dt:SQL="`echo \"$presql$presql2$sql\"|tr -d '\n'`"
3775 sqlog<<-EOF
3776 *** SQL-file: $sqlfile ***
3777 `cat $sqlfile`
3778 EOF
3779 if [ "$ddd" ]; then # REMOVE this block until 2019/7/1
3780 err "----- `gdate +%FT%T.%3N` ------------555555aaaaa"
3781 cat $sqlfile >> tmp/sql
3782 # query ".read $sqlfile" > $tmpd/foo
3783 sqlite3 -header -cmd 'pragma foreign_keys=ON' $db ".read $sqlfile" > $tmpd/foo
3784 cp $tmpd/foo tmp/
3785 err "----- `gdate +%FT%T.%3N` ------------555555"
3786 ## $ddd LINE exists at the end of this function
3787 fi
3788 printf '.mode list\n.header 0\n' >> $sqlfile
3789 cat<<EOF | sed "s,\(<TR><TD>\)\([1-9][0-9]*\)\(#[0-9a-fxs]*\)*</TD>,\1$elink$dvlink</TD>," | dt_rowhack
3790 <div> <!-- for folding by check button (s4-funcs.sh:dumptable()) -->
3791 <div class="dumptable">
3792 <table class="b$dt_class">
3793 `query ".read $sqlfile"`
3794 </table>
3795 </div> <!-- dumptable -->
3796 </div> <!-- for folding by check button (s4-funcs.sh:dumptable()) -->
3797 EOF
3798 ### `query ".read $sqlfile"`
3799 ### `sq -header -cmd ".mode $1" $db ".read $sqlfile"`
3801 ## REMOVE THIS!
3802 [ "$ddd" ] && err "----- `gdate +%FT%T.%3N` ------------666666"
3805 clean-orphaned() {
3806 # This shoud be done by foreign_key rules, but some db lack them
3807 query<<-EOF
3808 -- Find blogs that have no parent
3809 WITH orphanedblog AS (
3810 SELECT blog.id,val FROM blog JOIN blog_s bs
3811 ON blog.id=bs.id AND key='owner'
3812 WHERE val NOT IN (SELECT gname FROM grp)
3813 AND val NOT IN (SELECT name FROM user)
3814 ) -- Remove them
3815 DELETE FROM blog WHERE id IN (SELECT id FROM orphanedblog);
3817 -- Find articles that have no parent blog
3818 WITH orphanedarticle AS (
3819 SELECT id FROM article
3820 WHERE blogid NOT IN (SELECT id FROM blog)
3821 ) -- Remove them
3822 DELETE FROM article WHERE id IN (SELECT id FROM orphanedarticle);
3823 EOF
3825 par2table() (
3826 # copy current parameters of par into destination table
3827 # $1=definition-file
3828 # Using $user and $session
3829 # Return value:
3830 # 0: Stored successfully
3831 # 1: Insufficient fillings
3832 # 2: No permission to modify the record
3833 # 3: Invalid rowid
3834 # 4: SUCCESS to delete
3835 # 5: Stop deletion for lack of confirm check
3836 # 6: Password length too short
3837 # 7: Password mismatch
3838 # 8: Old password incorrect
3839 # 9: Duplicated post
3840 rowid=`getpar rowid`
3841 if [ ! -e $1 ]; then
3842 echo "テーブル定義ファイルが見付かりません" | html p
3843 exit 1
3844 fi
3845 tbl=${1%.def}
3846 tbl=${tbl##*/}
3847 if [ -n "$rowid" ]; then # Modify existing entry
3848 if [ x"$tbl" = x"user" ]; then
3849 rowowner=`query "select name from $tbl where rowid=$rowid;"`
3850 elif [ x"$tbl" = x"grp" ]; then
3851 sql="select gname from $tbl where rowid=$rowid;"
3852 ##err p2t:grp:q $sql
3853 isgrpowner "$user" "`query $sql`" && rowowner=$user
3854 elif [ x"$tbl" = x"blog" ]; then
3855 # Check if owner in blog_s
3856 blogowner=`getvalbyid blog owner "$rowid"`
3857 if isgrpowner "$user" "$blogowner"; then
3858 rowowner=$user
3859 else
3860 rowowner=`query "SELECT author FROM $tbl WHERE rowid=$rowid;"`
3861 fi
3862 else
3863 # 2016-12-05 There's no owner column in $tbl (need confirmation)
3864 rowowner=`query "SELECT author FROM $tbl WHERE rowid=$rowid;"`
3865 fi
3866 ### err rowowner=$rowowner
3867 if [ x"$user" != x"$rowowner" ]; then
3868 echo "他人のレコードはいじれないの" | html p
3869 return 2
3870 elif [ -z "$rowowner" ]; then
3871 echo "指定したレコードはないみたい" | html p
3872 return 3
3873 fi
3874 rm=`getpar rm` cfm=`getpar confirm`
3875 # Editing existent entry
3876 if [ x"$rm" = x"yes" ]; then
3877 if [ x"$rm$cfm" = x"yesyes" ]; then
3878 query "delete from $tbl where rowid=$rowid;"
3879 if [ x"$tbl" = x"grp" -o x"$tbl" = x"blog" ]; then
3880 clean-orphaned
3881 fi
3882 return 4
3883 else
3884 echo "消去確認のチェックがないので消さなかったの..." | html p
3885 return 5
3886 fi
3887 fi
3888 fi
3890 ts=${tbl}_s tm=${tbl}_m val="" pval="" formaster=""
3891 if [ -n "$rowid" ]; then
3892 # Update of existing record
3893 for col in `gettblcols $tbl`; do
3894 val=`getparquote $col`
3895 [ -z "$val" ] && continue
3896 ## err query "update $tbl set $col=$val where rowid=$rowid"
3897 ## XX: THIS IS DIRTY hack to ensure non-foreign key in blog_s
3898 sql="update $tbl set $col=$val where rowid=$rowid;"
3899 if [ x"$tbl" = x"grp" -a x"$col" = x"gname" \
3900 -o x"tbl" = x"user" -a x"$col" = x"name" ]; then
3901 ## User name cannot be changed with interface provided with this
3902 ## script. But we offer the trigger to change owner user
3903 ## of blog_s table.
3904 #err "select quote($col) from $tbl where rowid=$rowid;"
3905 old=`query "select quote($col) from $tbl where rowid=$rowid;"`
3906 cat<<-EOF | query
3907 -- Here we cannot use BEGIN-COMMIT because groupupdate()
3908 -- should use EXCLUSIVE transaction outside of this.
3909 SAVEPOINT par2table;
3910 $sql
3911 update blog_s set val=$val
3912 where key='owner' and val=$old;
3913 RELEASE SAVEPOINT par2table;
3914 EOF
3915 ## XX: DIRTY Hack Ends here
3916 ## We should keep blog's owner as a single column which has
3917 ## foreign key constraint with primary key of grp/user.
3918 else
3919 query "$sql"
3920 fi
3921 done
3922 # Then, set up $pval for further insertion of tbl_s and tbl_m
3923 for col in `gettblpkey $tbl`; do
3924 val=`query "select $col from $tbl where rowid=$rowid;"|sed -e 's/\"/\"\"/g'`
3925 pval="$pval${pval:+, }\"$val\""
3926 done
3927 else
3928 # New entry
3929 # XXX: WORK-AROUND FOR SOME STUPID BROWSER
3930 # Avoid empty repost of article.
3931 if [ x"$tbl" = x"article" ]; then
3932 # If rowid is empty and ID exists in article-table, that is REPOST!
3933 aid=`getpar id`
3934 xaid=`query "SELECT id FROM $tbl WHERE id='$aid';"`
3935 if [ -n "$xaid" ]; then
3936 # REPOST of article
3937 html p <<-EOF
3938 書き込み直後のリロードなので上書きを回避します。
3939 最新記事は末尾の「再読み込み」ボタンから見てください。
3940 EOF
3941 err "Repost aid=$aid Browser=[$HTTP_USER_AGENT] user=$user"
3942 return 9 # STOP Duplicated posting
3943 fi
3944 fi
3945 # Generate values() for primary keys
3946 for col in `gettblpkey $tbl`; do
3947 # Genuine primary keys for _m and _s
3948 val=`getvalquote $tbl $col`
3949 [ -z "$val" ] && continue
3950 pval="$pval${pval:+, }$val"
3951 done
3952 ##err pval=$pval
3953 for col in `gettblfkey $tbl`; do
3954 # args for values() to insertion into master table
3955 val=`getvalquote $tbl $col`
3956 [ -z "$val" ] && continue
3957 formaster=$formaster"${formaster:+, }$val"
3958 done
3959 formaster="$pval${formaster:+, }$formaster"
3960 ## err formaster=$formaster
3961 if [ -z "$formaster" ]; then
3962 echo "項目を全て埋めてください" | html pre
3963 return 1
3964 fi
3965 ## err "replace into $tbl values($formaster);"
3966 query "replace into $tbl values($formaster);"
3967 ## Insertion to master table, done
3968 fi
3970 transaction=$tmpd/sqlfile.sql; touch $transaction
3971 for kt in s m; do
3972 tb2=${tbl}_$kt
3973 for col in `gettbl_${kt}_cols $tbl`; do
3974 ptype=`getpartype $col "limit 1"`
3976 # First, check update of existing entries in _m
3977 if [ $kt = m ]; then
3978 # sessID|address.1.22|string|Somewhere-x.y.z
3979 sql=""
3980 ##err dots from query "select var from par where var like '$col.%';"
3981 for v in `query "select var from par where var like '$col.%' AND sessid='$session';"`; do
3982 # v=address.1.22
3983 st_rowid=${v##*.}
3984 origcol=${v%%.*} # original column derived from
3985 ##err Updating for $v st_rowid=$st_rowid, partype=`getpartype $v`
3986 ##case `getpartype $v` in
3987 ## err CASE `gettbl_coltype $tbl/$origcol` in
3988 ## err edit flag = `getpar action.$v`
3989 case `getpar action.$v` in
3990 rm)
3991 if [ x`getpar confirm.$v` = x"yes" ]; then
3992 newsql="delete from $tb2"
3993 else
3994 echo "削除確認未チェック" | html p
3995 fi ;;
3996 edit)
3997 case `gettbl_coltype $tbl/$origcol` in
3998 image|document|binary)
3999 file=$tmpd/`getparfilename $v`
4000 if [ ! -s "$file" ]; then # Maybe stupid REPOST
4001 err "Empty REPOST by [$HTTP_USER_AGENT] user=$user"
4002 continue
4003 fi
4004 ## err type=file=$file
4005 [ -z "$file" ] && continue
4006 bn=`sqlquotestr "${file##*/}"`
4007 bin="X'"$(hexize "$file")"'"
4008 ct=`file --mime-type - < "$file" |cut -d' ' -f2`
4009 type=\"file:$ct\"
4010 newsql="update $tb2 set val=$bn, type=$type, bin=$bin"
4011 cachedir=`getcachedir "$tbl/$rowid"`
4012 err getcache tbl/rowid=$tbl/$rowid, rm -r $cachedir
4013 rm -rf $cachedir
4014 ;;
4015 *)
4016 newsql="update $tb2 set val=(select val from par where var \
4017 like '$col.%.$st_rowid' AND sessid='$session')"
4018 ;;
4019 esac
4020 ;;
4021 mv)
4022 # regularize filename and strip directory part
4023 newname=`getpar mv.$v|tr -d '":;#<>?^%$!'|tr -d "'"|tr ' ' _`
4024 newname=`basename $newname`
4025 err "newname($v)=$newname"
4026 newext="${newname##*.}"
4027 if test x"$newname" != x"$newext" \
4028 && echo "${file_accept#*=}" \
4029 | fgrep -w "$newext" >/dev/null 1>&2
4030 then
4031 newsql="UPDATE $tb2 SET val='$newname'"
4032 else
4033 html p<<-EOF
4034 $newname は取り扱えないファイル名です。
4035 空白を含まない名前で拡張子を正しくつけて下さい。
4036 EOF
4037 continue
4038 fi
4039 ;;
4040 *) # maybe "keep", do not modify value
4041 continue
4042 ;;
4043 esac
4044 # err newsql=$newsql
4045 sql=$sql$nl"$newsql where rowid=$st_rowid;"
4046 done
4048 if [ x"$bin" = x"NULL" ]; then
4049 ## err repl:normal sql=`echo $sql`
4050 if [ -n "$transaction" ]; then
4051 cat<<-EOF >> $transaction
4052 $sql
4053 DELETE FROM $tb2 WHERE type='string' AND val='';
4054 EOF
4055 else
4056 query "$sql
4057 delete from $tb2 where type='string' and val='';"
4058 ## err repl:normal done
4059 fi
4060 else
4061 # Binary update line is TOO LONG to pipelining
4062 sqlfile="$tmpd/sqlf.$$"
4063 if [ -n "$transaction" ]; then
4064 echo "$sql" >> $transaction
4065 else
4066 echo "$sql" > $sqlfile
4067 query ".read $sqlfile"
4068 fi
4069 fi
4070 # Rest of kt==m: set multiple mode
4071 nr=`getparcount $col`
4072 else
4073 nr=1 # for kt==s, number of records is 1
4074 fi
4076 i=0
4077 while [ $i -lt $nr ]; do
4078 limit="limit 1 offset $i"
4079 i=$((i+1)) # increase beforehand against continue
4080 val=`getvalquote $tbl $col "$limit"`
4081 [ -z "$val" -o x"$val" = x'""' -o x"$val" = x"NULL" ] && continue
4082 ## err $col=$val
4083 bin=NULL
4084 ## err partype$col=`getpartype $col "$limit"`
4085 case $ptype in
4086 file) file=$tmpd/`getparfilename $col "$limit"`
4087 ## err parfile-$col=$file
4088 [ -z "$file" ] && continue
4089 bin="X'"$(hexize "$file")"'"
4090 ct=`file --mime-type - < "$file"|cut -d' ' -f2`
4091 type=\"file:$ct\" ;;
4092 "*"*) continue ;; # foreign table
4093 *) type=\"string\" ;;
4094 esac
4095 case `gettbl_coltype $tbl/$col` in
4096 password) # special care for password
4097 # name={password,pswd1,pswd2}
4098 p1=`getpar pswd1 "$limit"`
4099 if [ -z "$p1" ]; then
4100 continue # SKIP password setting, if p1 is empty
4101 else
4102 pswd=`getpar pswd "$limit"` p2=`getpar pswd2 "$limit"`
4103 ## err pswd=$pswd
4104 if pwcheck "$pswd"; then
4105 if [ x"$p1" = x"$p2" ]; then
4106 case "$p1" in
4107 ??????????*) ;;
4108 *) echo "パスワードは10字以上にしてください。" | html p
4109 return 6;;
4110 esac
4111 val="\"`echo $p1|mypwhash`\""
4112 else
4113 echo "2つの新パスワード不一致" | html p
4114 return 7
4115 fi
4116 else
4117 echo "旧パスワード違います" | html p
4118 return 8
4119 fi
4120 fi
4121 ;;
4122 esac
4123 ## err p2t: "replace into $tb2 values($pval, \"$col\", $type, $val, bin...);"
4124 #query "replace into $tb2 values($pval, \"$col\", $type, $val, $bin);"
4125 sql="replace into $tb2 values($pval, \"$col\", $type, $val, $bin);"
4126 if [ x"$bin" = x"NULL" ]; then
4127 ## err Normal-query: `echo $sql`
4128 if [ -n "$transaction" ]; then
4129 echo "$sql" >> $transaction
4130 else
4131 query "$sql"
4132 fi
4133 else
4134 sqlfile="$tmpd/query.$$"
4135 ## err sqlfile=`ls -lF $sqlfile`
4136 if [ -n "$transaction" ]; then
4137 echo "$sql" >> $transaction
4138 else
4139 echo "$sql" > $sqlfile
4140 query ".read $sqlfile"
4141 fi
4142 fi
4143 ## err p2t done
4144 done
4145 done
4146 done
4147 [ -n "$transaction" -a -s "$transaction" ] && cat <<-EOF | query
4148 -- We cannot use transaction here, because groupupdate may use it.
4149 SAVEPOINT pa2table_insert;
4150 .read $transaction
4151 RELEASE SAVEPOINT pa2table_insert;
4152 EOF
4153 rc=$?
4154 [ $rc -eq 0 -a x"$tbl" = x"user" ] && touch $userupdateflag
4155 ## err "Table:$tbl update done "
4156 return $rc
4158 genform() {
4159 # $1 = form definition file
4160 # $2, $3 (optional)= table name and ROWID
4161 # If $GF_VIEWONLY set and nonNull, output values without form
4162 # If $GF_ARGS set, use it as content-strings in the form
4163 # If $GF_OWNER set, use it as value of name="owner"
4164 # If $GF_STAGE set, use it as value of name="stage"
4165 forms="" hiddens="" rowid=$3
4166 if [ ! -e "$1" ]; then
4167 echo "そのようなデータベースはないようです($2)。" | html p
4168 return
4169 elif [ -n "$2" ]; then
4170 rec=`query "select * from $2 where rowid='$rowid';"`
4171 if [ -z "$rec" ]; then
4172 pk=`gettblpkey $2`
4173 ###rec=`sq $db "select rowid from $2 where $pk='$rowid'"`
4174 rec=`query "select rowid from $2 where $pk='$rowid';"`
4175 rowid=$rec
4176 rec=$3
4177 fi
4178 if [ -z "$rec" ]; then
4179 echo "そんなレコードはないみたいね..." | html p
4180 return
4181 fi
4182 fi
4183 if [ -z "$GF_VIEWONLY" ]; then
4184 rm='<input id="rm" name="rm" type="checkbox"
4185 value="yes"><label for="rm">このエントリの削除</label>
4186 <span>ほんとうに消しますよ(確認)!
4187 <input name="confirm" type=checkbox value="yes">はい</span>'
4188 fi
4189 # Image Cache dir
4190 ## err genform: getcache=$2/$rowid
4191 td=`getcachedir "$2/$rowid"`
4192 while IFS=: read prompt name keytype type args; do
4193 [ -z "${prompt%%\#*}" ] && continue # skip comment line(#)
4194 sp="${args:+ }"
4195 form="" val=""
4196 if [ -n "$rowid" ]; then
4197 # err genform2a: Seeking for "$2.$name, type=$type"
4198 rawval=`getvalbyid $2 $name $rowid $td`
4199 val=`echo "$rawval"|htmlescape`
4200 ## err genform3a: getvalbyid $2 $name $rowid $td
4201 ## err genform3b: val="[$val]" type="$type"
4202 fi
4203 if [ -n "$GF_VIEWONLY" ]; then
4204 is_hidden "$2" "$name" && continue
4205 fi
4206 case "$type" in
4207 text*)
4208 cgiform=cgi_multi_$type
4209 if [ -s $td/$name.count -a -n "$val" ]; then
4210 form=`$cgiform $name $td`
4211 val=$(echo "$val"|
4212 while read fn; do
4213 echo "<tr><td>`cat $td/$fn|htmlescape|hreflink`
4214 </td></tr>$nl"
4215 done)
4216 val="<table>$nl$val$nl</table>"
4217 else
4218 #form="<input name=\"$name\" value=\"$val\" type=\"$type\"$sp$args>$nl"
4219 form=`cgi_$type $name "$rawval" "$args"`
4220 fi
4221 ;;
4222 [Rr][Aa][Dd][Ii][Oo])
4223 fh="<label><input type=\"radio\" name=\"$name\""
4224 form="`echo $args|sed -e \
4225 \"s,\([^ =][^=]*\)=\([^= ][^= ]*\),$fh value=\\"\2\\">\1</label>,g\"`"
4226 ;;
4227 [Cc][Hh][Ee][Cc][Kk][Bb][Oo][Xx])
4228 form="<label><input type=\"checkbox\" name=\"$name\" value=\"${args#*=}\">${args%=*}</label>"
4229 ;;
4230 [Ss][Ee][Ll][Ee][Cc][Tt])
4231 fh="<select name=\"$name\">$nl"
4232 form=$(for l in $args; do
4233 echo "<option value=\"${l#*=}\">${l%=*}</option>"
4234 done)
4235 if [ -n "$val" ]; then
4236 form=`echo $form|sed -e "s,\(value=.$val.\),\\1 selected,"`
4237 fi
4238 form="$fh$form</select>"
4239 ;;
4240 [Ii][Mm][Aa][Gg][Ee]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Bb]inary)
4241 if [ -s $td/$name.count ]; then
4242 form=`cgi_multi_file $name $td "$args"`
4243 if [ -n "$val" ]; then
4244 hrfb="$myname?showattc+$2_m"
4245 val=$(echo "$rawval" \
4246 | while read fn; do
4247 data=`percenthex "$td/$fn"`
4248 #ct=`cat $td/$fn.content-type`
4249 ct=`file --mime-type - < "$td/$fn"|cut -d' ' -f2`
4250 ri=`cat "$td/$fn.rowid"`
4251 ## err fn=$fn, name=$name, ri=$ri; ls -lF "$td/" 1>&3
4252 #imgsrc="<img src=\"data:$ct,$data\">"
4253 #echo "<a href=\"$hrfb+$ri\">$imgsrc</a><br>"
4254 iconhref2 "$td/$fn" "$hrfb+$ri" ""
4255 done)
4256 fi
4257 else
4258 form="<input type=\"file\" name=\"$name\" $args>"
4259 if [ -n "$val" ]; then
4260 imgs=$(echo "$rawval"\
4261 |while read fn;do
4262 data=`percenthex "$td/$fn"`
4263 echo "<img src=\"data:image/png,$data\">$fn<br>"
4264 done)
4265 form=$form"<br>$imgs"
4266 val=$imgs # 2015-06-15
4267 else
4268 form="<input type=\"file\" name=\"$name\" $args>"
4269 fi
4270 fi
4271 ;;
4272 [Hh][Ii][Dd][Dd][Ee][Nn])
4273 if [ -n "$GF_STAGE" -a x"$name" = x"stage" ]; then
4274 args="value=\"$GF_STAGE\""
4275 fi
4276 form="<input type=\"hidden\" name=\"$name\" $args>"
4277 prompt='' # Remove prompt
4278 ;;
4279 [Aa][Uu][Tt][Hh][Oo][Rr])
4280 [ -n "$GF_VIEWONLY" ] && continue
4281 form="<input type=\"hidden\" name=\"author\" value=\"$user\">"
4282 prompt="" ;;
4283 [Oo][Ww][Nn][Ee][Rr])
4284 [ -n "$GF_VIEWONLY" ] && continue
4285 val=${GF_OWNER:-$val}
4286 val=${val:-$user}
4287 form="<input type=\"hidden\" name=\"owner\" value=\"$val\">"
4288 prompt="" ;;
4289 [Uu][Ss][Ee][Rr])
4290 # XXX: is null $user ok?
4291 #form="<input type=\"hidden\" name=\"user\" value=\"$user\">"
4292 [ -n "$GF_VIEWONLY" ] && continue
4293 form="$user"
4294 ;;
4295 [Pp]assword)
4296 [ -n "$GF_VIEWONLY" ] && continue
4297 form="`cgi_passwd`"
4298 val=""
4299 ;;
4300 [Ss][Ee][Rr][Ii][Aa][Ll]|[Ss][Tt][Aa][Mm][Pp])
4301 [ -n "$GF_VIEWONLY" ] && continue
4302 if [ -z "$rowid" ]; then
4303 val=`genserial`
4304 fi
4305 form="<input type=\"hidden\" name=\"$name\" value=\"$val\">"
4306 prompt="" ;;
4307 [Ss][Ee][Ss][Ss][Ii][Oo][Nn])
4308 prompt=""
4309 ;;
4310 parent|path|blog*)
4311 prompt=""
4312 ;;
4313 "*"*)
4314 tail=$tail"``"
4315 continue ;;
4316 esac
4317 if [ -n "$prompt" ]; then
4318 if [ -n "${GF_VIEWONLY}" ]; then
4319 form=$val
4320 else
4322 fi
4323 forms=$forms" <tr class=\"$name\"><th>$prompt</th><td>$form</td></tr>$nl"
4324 else
4325 hiddens=$hiddens$nl"$form"
4326 fi
4327 done < $1
4328 # enctype="multipart/form-data"
4329 cat<<EOF
4330 <form action="${GF_ACTION:-$myname}" method="POST" enctype="multipart/form-data">
4331 ${rowid:+$rm}
4332 <table class="b $2">
4333 $forms
4334 </table>$hiddens
4335 ${GF_STAGE:+`cgi_hidden stage $GF_STAGE`}
4336 ${rowid:+<input type="hidden" name="rowid" value="$rowid">}
4337 EOF
4338 if [ -z $GF_VIEWONLY ]; then
4339 cat<<EOF
4340 <input type="submit" name="sub" value="OK">
4341 <input type="reset" name="res" value="Reset">
4342 EOF
4343 fi
4344 cat<<EOF
4345 $GF_ARGS</form>
4346 $tail
4347 EOF
4349 edittable() {
4350 # $1=form-def $2=table $3 rowid
4351 genform "$@"
4353 viewtable() {
4354 GF_VIEWONLY=1 genform "$@"
4356 showattc() {
4357 # $1=table_m $2=rowid &optional $3=RawFlag
4358 ## err \$1=$1 \$2=$2 \$3=$3
4359 if ! isfilereadable $user $1 $2; then
4360 contenttype; echo
4361 echo "このファイルは管理者のみしか見られません" | html p
4362 putfooter; exit
4363 fi
4364 idir=`umask 002; mktempd` || exit 1
4365 # tmpfiles=$tmpfiles"${tmpfiles+ }$idir"
4366 bin=$idir/$myname-$$.bin
4367 sql="select quote(bin) from $1 where rowid='$2';"
4368 ## err showattc: sql: $sql
4369 sq $db "$sql" | unhexize > $bin
4370 tv=`query "select type||'//'||val from $1 where rowid='$2';"`
4371 type=${tv%//*} fn=${tv#*//}
4372 ## err tv=$tv type=$type fn=$fn, tp2=${tv%\|*}
4373 ct=${type#file:}
4374 case $ct in # all text/* changed to text/plain
4375 text/*)
4376 charset=`nkf -g $bin|cut -d' ' -f1`
4377 case $charset in
4378 ASCII*) charset="" ;;
4379 esac
4380 if [ -z "$3" ]; then
4381 ct="text/html${charset:+; charset=$charset}"
4382 link="?showattc+$1+$2+raw"
4383 nkf -e $bin | htmlescape | nkf --oc="$charset" \
4384 | sed 's,^,<span></span>,' \
4385 | _m4 -D_TITLE_="$fn" -D_CONTENT_TYPE_="$ct" \
4386 -D_LINK_="$link" \
4387 -D_BODY_="syscmd(\`cat')" $layout/pretty.m4.txt
4388 exit $?
4389 fi
4390 ct="text/plain${charset:+; charset=$charset}"
4391 ;;
4392 esac
4393 contenttype "$ct"
4394 echo "Content-Disposition: filename=\"$fn\""
4395 echo "Content-Length: " `cat $bin | wc -c`; echo
4396 #echo "Content-Type: " ${type#file:}; echo
4397 cat $bin
4400 # Some default stupid handler on CGI values
4402 default_storedb() {
4403 # ARG: $1=table-def-file
4404 # RET: $tbl=table-name, $col=mail-column, $cols=columns
4405 tbl=`basename $1`
4406 tbl=${tbl%.def}
4407 cols="`grep :text $1|cut -d: -f2`"
4408 col=`echo "$cols"|head -1`
4409 vcol=`getpar $col`
4410 err default0: \$1=$1 col=$col cols="[$cols]" vcol=$vcol
4411 if [ -n "$vcol" ]; then
4412 par2table $1
4413 else
4414 return 2 # No insertion occurred
4415 fi
4418 default_view() { # $1=def-file
4419 ### DT_VIEW="edittable+$tbl" dumptable html $tbl "$cols" \
4420 ## DT_VIEW="edittable+$tbl" dumptable html $tbl "name memo file" \
4421 default_storedb "$@"
4422 query "select rowid from $tbl order by rowid desc;" \
4423 | while read rowid; do
4424 viewtable $1 $tbl $rowid
4425 done | _m4 -D_TITLE_="$tbl" \
4426 -D_FORM_="`genform $1`" \
4427 -D_DUMPTABLE_="syscmd(cat)" \
4428 $layout/html.m4.html $layout/form+dump.m4.html
4430 default_viewtext() { # $1=def-file
4431 ### DT_VIEW="edittable+$tbl" dumptable html $tbl "$cols" \
4432 default_storedb "$@"
4433 DT_VIEW="viewtable+$tbl" dumptable html $tbl "name memo file" \
4434 | _m4 -D_TITLE_="$tbl" \
4435 -D_FORM_="`genform $1`" \
4436 -D_DUMPTABLE_="syscmd(cat)" \
4437 $layout/html.m4.html $layout/form+dump.m4.html
4439 default_smail() {
4440 default_storedb "$@"
4441 if [ $? -eq 2 ]; then
4442 _m4 -D_TITLE_="入力" \
4443 -D_FORM_="`genform $1`" \
4444 -D_DUMPTABLE_="" \
4445 $layout/html.m4.html $layout/form+dump.m4.html
4446 return
4447 fi
4448 cond=""
4449 for pk in `gettblpkey $tbl`; do
4450 pv=$(sqlquote "$(getpar $pk)")
4451 cond="$cond${cond:+ and }$pk=$pv"
4452 done
4453 sql="select rowid from $tbl where $cond;"
4454 rowid=`query "$sql"`
4455 ## err smail1 - "$sql" "-> rowid=$rowid"
4457 while IFS=: read prompt name keytype type args; do # Read from $1
4458 val=`getpar $name`
4459 if [ -n "$val" ]; then
4460 text="$text
4461 $prompt
4462 $name=$val
4463 ---------------------------------------------------------"
4464 fi
4465 case "$type" in
4466 image|document|file)
4467 fn="`getvalbyid $tbl $name $rowid $tmpd`"
4468 fns=$(echo "$fn"|while read fn; do
4469 err mv $tmpd/$fn.orig $tmpd/$fn
4470 mv $tmpd/$fn.orig $tmpd/$fn
4471 rm $tmpd/$fn.rowid # Remove cache flag
4472 ## err "`ls $tmpd/$fn`"
4473 echo $fn
4474 done)
4475 files="$files $fns"
4476 ;;
4477 esac
4478 done < $1
4479 ## err FILES=$files "`ls -lF $tmpd`"
4480 subj="from ${REMOTE_ADDR}"
4481 (echo "$url"
4482 echo "への書き込みがありました。"
4483 echo "------"
4484 echo "$text"
4485 ) | (cd $tmpd &&
4486 err LS="`ls -lF`" &&
4487 $mydir/sendmultipart.sh -t "$admin" -s "$subj" $files)
4488 _m4 -D_TITLE_="入力完了" $layout/html.m4.html
4489 echo "以下の内容で送信しました。" | html p
4490 viewtable $1 $tbl \
4491 `query "select rowid from $tbl order by rowid desc limit 1;"`
4492 echo "戻る" | html a "href=\"?\""