s4

view s4-funcs.sh @ 1027:f389a311a8d4

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