s4

view s4-funcs.sh @ 830:0f947210a094

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