s4

view s4-funcs.sh @ 1028:6e24f1ecf13e

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