s4

view s4-funcs.sh @ 370:0cfba5e4c7a3

Display mode-column in blog listing
author HIROSE Yuuji <yuuji@gentei.org>
date Sat, 26 Nov 2016 09:49:00 +0859
parents e25b701dac6c
children 302eae8f723b
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 myname=`basename ${SCRIPT_NAME:-$0}`
8 mydir=`dirname ${SCRIPT_FILENAME:-$0}`
9 myargs="$@"
10 PATH=/usr/local/sqlite3/bin:/usr/local/vim7/bin:/usr/iekei/ImageMagick/bin:/usr/local/ImageMagick/bin:$PATH
11 tmpdir=${TMPDIR:-tmp}
12 dbdir=${DBDIR:-db}
13 tmpfiles=""
14 db=${DB:-$dbdir/cgi.sq3}
15 admin=${ADMIN:-hostmaster@example.org}
16 templ=${TEMPL:-templ}
17 layout=${LAYOUT:-$templ/default}
18 formdir=${FORMDIR:-$templ/form}
19 imgdir=${IMGDIR:-img}
20 url=${URL:-"${REQUEST_SCHEME:-http${HTTPS:+s}}://$HTTP_HOST$REQUEST_URI"}
21 urlbase=${url%%\?*}
22 msgdir=$templ/msg
23 timeout="+2 days"
24 memoplimitdays="7"
25 dumpcollen=22
26 #thumbxy=120x120
27 thumbxy=96x96
28 iconxy_S=80x80
29 iconxy_M=400x400
30 maximagexy=1600x1600
31 ### maximagexy=400x400
32 file_accept='accept="image/*,text/*,audio/*,application/vnd.oasis.*,application/pdf,application/x-*"'
33 blogreadflagrowid=0
34 querylog=$tmpdir/query.log
36 tconfs=""
37 imgcached=cache/img.`date +%Y/%m`
38 conftbl=_tblconf
39 nl="
40 "
41 iconcachekey="profimgcache_S"
42 case "$HTTP_USER_AGENT" in
43 *i[Pp]hone*|*[Aa]ndroid*) touchpanel=1 ;;
44 *) touchpanel="" ;;
45 esac
46 . ./s4-cgi.sh
48 : <<EOF
50 !! 検索等でblogテーブル参照時は sql4readableblogs() で定義される
51 !! readableblogs テーブルを使うこと
52 資料配布、グループ管理・ML、ファイル交換、クリッカー、アンケート
53 レポート提出管理
54 ひとつのarticleをheadingにして新規ツリーを作成、あるといいかも。
56 [2016]
57 7/12 根本への反省
58 * cgi自身の $1, $2 での切り替えでなく、CGI変数での受け渡しにすべき。
59 arg1/arg2/arg3 的に $1 に / 区切りでつけた方がよかったかな。
61 [以下2015]
62 8/4 ○グループに承認加入モードを追加
63 ○グループに参加していない場合は grpaction できない
64 Web
65 締切設定
67 8/2 ○s4.cgi生成系 → index.cgi生成
68 ○自分の提出物リスト
70 7/19 ○設置
71 ○一斉送信
72 ○getparfilename の tmpd の扱い
73 ○やっぱりs4にしようかな
74 7/18 ○書込著者からホームへのリンク
75 7/17 ○個人blogに「レポート提出用」がついたときの挙動
76 ○添付ファイル回収
77 ○imgcacheは別ディレクトリにしないと + .htaccess
78 7/15 ○レポート提出モードの表示を付ける
79 管理者権限での削除? → まだいいか
81 7/13 ○前回アクセス基準の新着数は欲しいなあ
82 ○レポート提出はどうしよう
83 → ○blogにモードを追加:
84 ○レポート提出モード
85 添付ファイル (誰が見たかログ)
86 クリッカーは別立てメニューにしないと(管理者がON/OFF)
87 ○添付ファイルの読み出し権(6/22から) ← モードで対処
90 7/9 ○管理者の追加
91 △グループメンバの操作 → 要不要を吟味
92 ○グループ情報編集の行先はそのグループがいい?
93 ○新規グループの作成はどこから入るか
94 △グループホームとユーザホームを揃える
96 7/8 ○グループ一覧をユーザ一覧と揃える。
98 7/6の次 ○グループのconf編集の入口
99 ○グループ検索
101 6/22の次 ○ホーム画面、○招待状、親記事追跡、○編集ボタン、削除ボタン、
104 6/7の次 ○blogを作ってみる || userconfig || _mのまとめ編集(削除)
105 6/7の次の次 ○userconfigの画面だけ作ってみる。
107 ○ 5/28の次 edittableに「削除」ボタンを足す
108 ○6/1 par2tableを triplex 対応に
109 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'));
110 →とすると 一気に
112 ## form.def を考えなおそう:
113 ## userのように必須カラムを決まった位置に付ける?
114 ## 必須カラム、owner(foreign key passwd(name)), update datetime
115 ## ユーザ管理とグループ管理はデフォルトで持たせてしまえ
117 ## 縦持ちデータの入力/編集を供給する関数 single + multi
118 ## 持てるテーブル構造はシステム標準5種 + ユーザ定義2種類
119 ## 1. passwd
120 ## 2. grp
121 ## 3. grp_mem
122 ## 4. topic 記事のIDとなる
123 ## 5. topic_cont 特定IDの記事の内容物
124 ## 6. list 繰り返し登場あり
125 ## 7. hash 繰り返し登場なし
127 ## ● listの定義:
128 ## create table list(id unique, parentID, type, value);
129 ## ● hashの定義:
130 ## create table hash(parentID, type, value, primary key(parentID, type));
132 ## グループ属性: community, friend
133 ## ○ blob使えるのかな。streamで行けるのか? xxdで行けた。ありがたい。
134 ## form-defとtableは1対1対応でいいか
135 ## csv2sq3 で .csv.sq3 の Makefile
137 ## 書き込みオブジェクトとは何か?
138 ## topic : id, belongto, title, owner, mode
139 ## type := root | comment
140 ## topic_cont : id, topicid(F), ppath, contenttype, filename, content,
141 ## unique(id, filename)
142 ## type := body(single) | attachment(multi)
144 ## group := name(P), tag, gecos, owner(F), mode
145 ## tag := personal | friend | ... any string
146 ## group_member := gname(F), type, name(F), UNIQUE(gname, type, name)
147 ## type := "u" | "g"
148 ## できたー!
149 ## 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';
150
151 ↓以下に変更
152 with recursive allmem as
153 (select gname,val from grp_m where gname='foo'
154 union all select grp_m.gname,grp_m.val from
155 grp_m,allmem where allmem.val=grp_m.gname)
156 select val from allmem where val in (select name from user);
159 with recursive allmem as
160 (select gname,val from grp_m where gname='foo'
161 union all select grp_m.gname,grp_m.val from grp_m,allmem
162 where allmem.val=grp_m.gname)
163 select a.*, coalesce(b.val,a.val) from allmem a left join grp_mem_s b
164 on a.gname=b.gname and a.val=b.user and b.key='email'
165 where a.val in (select name from user);
168 ## triggerもできた。
169 ## 5/22から:グループ作成画面
170 ## 埋め込み画像 data:CONTENT-TYPE;base64,.....
172 ## 考え得るノードタイプ
173 ## 日報 - 個人所属かグループ所属か
174 ## 課題提出 - 個人所属かグループ所属か
175 ## グループ管理
176 ## 個人情報管理
177 ##
179 ## 例: group:sip - topic:1:sip:Aperture:yuuji:rw
180 ## - topic:2:sip:ISO:yuuji:rw
181 ## topic_cont 1:1:/:body:text...Aperture
182 ## 2:1:/1:body:text..Aperture
183 ## 3:1:/1:attachment:binary..Aperture
184 ## 4:1:/2:body:text..Aperture
185 ## 5:1:/2:attachment:binary..Aperture
186 ## 6:2:/:body:text..ISO
187 ## 7:2:/6:body:text..ISO
188 ## 8:2:/6:attachment:binary..
190 ## ログテーブル
191 ## time, who, action, tbl, id idなんか取れるかな
195 ■表設計
196 * 3つの表に分散管理
197 id格納表 + hash表 + list表
198 * *_s *_m
202 user, user_map, user_col
204 ■抽象エントリタイプ
205 * user
206 idとして機能 → table中の owner に自動挿入(?)
207 * group
208 権限判定に利用
209 * serial
210 自動idとして機能
211 * password
212 入力 type=passwordで入力
213 変更 oldpasswd, password×2 で確認後修正
214 * session
215 password認証後のセッションキーとして機能
216 * text
217 入力 type=text
218 * textarea
219 入力 textarea
220 * image|document
221 入力 type=fileで入力し、mime-typeを確認
222 * owner
223 入力時の $user で、外部キー制約が付く
224 * gowner
225 グループとしての所有者で、外部キー制約が付く
226 * timestamp
227 datetime()
228 * parent
229 木構造の場合の親の位置
230 * path
231 木構造の場合の自分の位置
233 格納タイプ
234 * list
235 表 parentID, key, val でUNIQUE(parentID, key, val)
236 * hash
237 表 parentID, key, val でUNIQUE(parentID, key)
239 オブジェクトタイプ
240 * entry
241 id, title, owner
242 * textpart
243 id, parentID, text
244 * binarypart
245 id, parentID, contenttype, filename, content
246 * content
247 hash(textpart), list(binarypart)
248 * topic
249 id, hash(content), list(reply)
250 * reply
251 id, parentID, content
252 * blog
253 list(entry)
254 blog = [topic, list(reply)]
257 blog = [ {"title" => "hoge", "owner" => "yuuji", "date" => "2015-04-27",
258 "text" => "hogehoge ..",
259 "reply" => [ {"serial" => 1,
260 "author" => "taro",
261 "date" => "2015-04-28",
262 "parent" => "/",
263 "path" => "/1",
264 "text" => "blah, blah, ....",
265 "image" => ["a.jpg", "b.jpg"] },
266 {"serial" => 2,
267 "author" => "hanako",
268 "date" => "2015-04-29",
269 "parent" => "/",
270 "path" => "/2",
271 "text" => "blah, blah, ....",
272 "image" => [] }]},
273 {"title" => "buha", ...} ]
276 user:=
277 ユーザ名(英数字):name:p:text:length="20" maxlength="40"
278 パスワード:pswd:s:password:length="20" maxlength="40"
279 説明(日本語OK):gecos:s:text:length="20" maxlength="40"
280 セッションキー:skey:s:session
281 メイルアドレス:email:m:text:length="20" maxlength="40"
282 住所:address:m:textarea:maxlength="400"
283 プロフィール画像:profimg:m:image:maxlength="400K"
284 履歴書:profpdf:m:document:maxlength="4M"
286 変換表
287 /user/email=m
289 blog:=
290 シリアル:id:p:serial
291 タイトル:title:s:text:
292 所有者:owner:s:owner:
293 時刻:ctime:s:stamp:
294 リード文:heading:s:textarea:
295 リプライ:reply:m:*article:
297 article:=
298 シリアル:id:p:serial
299 筆者:author:s:owner
300 時刻:ctime:s:stamp:
301 参照元:parent:s:parent:
302 パス:path:s:path:
303 本文:text:s:textarea:
304 画像:image:m:image:
306 履歴書:profpdf:m:document:maxlength="4M"
309 EOF
311 sq() {
312 # ./args.rb -cmd ".timeout 3000" "$@"
313 sqlite3 -cmd 'PRAGMA foreign_keys=ON' -cmd ".timeout 3000" "$@"
314 }
315 dbsetup() {
316 [ -d $tmpdir ] || mkdir -m 1777 $tmpdir
317 [ -d $dbdir ] || mkdir -m 1775 $dbdir
318 sqi=$tmpdir/sqi.$$
319 sqo=$tmpdir/sqo.$$
320 mkfifo $sqi $sqo
321 #tail -f $sqi | sq $db & # "tail -f" is too heavy. DO NOT USE!!
322 sq $db < $sqi &
323 sq3pid="`jobs -p` $!"
324 exec 2>> $tmpdir/error.out
325 exec 3>> $tmpdir/debug.out
326 exec 5> $sqi # Turning $sqi access through fd5 for continuous open state
327 rm $sqi
328 }
329 cleanup() {
330 trap '' INT HUP EXIT TERM PIPE
331 echo .quit >&5
332 kill $sq3pid
333 kill $sq3pid
334 rm -f $sqo $sqi
335 rm -rf $tmpfiles
336 }
337 # We want to use piped function to put querylog, but we use
338 # simple redirection for the sake of speed.
339 query() {
340 echo ".once $sqo" >&5
341 echo "`date '+%F %T'`:[${user:-NULL}] <<<" >> $querylog
342 if [ -z "$1" ]; then
343 tee -a $querylog
344 else
345 echo "$@" >> $querylog
346 echo "$@"
347 fi >&5
348 cat $sqo
349 echo '>>>' >> $querylog
350 }
351 _m4() {
352 #_S4NAME_=f,f,f
353 m4 ${_S4NAME_:+"-D_S4NAME_=${_S4NAME_}"} "$@"
354 }
355 ismember() {
356 # $1=user, $2=group
357 err ismem: "select user from grp_mem where gname=$(sqlquote $2) and user='$1';"
358 test -n "`query \"select user from grp_mem where gname=$(sqlquote $2) and user='$1';\"`"
359 }
360 isuser() { # Check if $1 is a valid user
361 test -n "`query \"select name from user where name='$1';\"`"
362 }
363 isgroup() { # Check if $1 is a valid group
364 err isgroup: "select gname from grp where gname=$(sqlquote $1);"
365 test -n "`query \"select gname from grp where gname=$(sqlquote $1);\"`"
366 }
367 isgrpowner() (
368 # $1=user, $2=group
369 gn=`sqlquote "$2"`
370 sql="select user from grp_adm where gname=$gn and user='$1';"
371 err isgrpowner: $sql
372 test -n "`query $sql`"
373 )
374 getgroupadminmails() {
375 # $1=group
376 for i in $(getgroupadmins $1); do
377 email4group "$1" "$i" ;
378 done
379 }
380 getgroupadmins() { # $1=group
381 # This function is called in a backquote, so needn't to be subshellized
382 qgrp=`sqlquote "$1"`
383 query "select user from grp_adm where gname=$qgrp;"
384 }
385 getgroupattr() { # $1=group $2=attr
386 # This function is called in a backquote, so needn't to be subshellized
387 getvalbyid grp $2 \
388 $(query "select rowid from grp where gname=`sqlquote $1`;")
389 }
390 getgroupbyid() {
391 # $1=id|gname
392 sql="select coalesce((select gname from grp where gname=$(sqlquote $1)),
393 (select gname from grp where rowid=$(sqlquote $1)));"
394 # err ggbyid: `echo $sql`
395 query $sql
396 }
397 isfilereadable() { # $1=user $2=tbl $3=rowid
398 # Return true if user($1) can read attachment files in tbl($2):rowid($3)
399 [ -z "$1" -o -z "$2" -o -z "$3" ] && return 1 # invalid argument
401 # Return true when anonymous mode
402 [ "$anonymousmode" ] && return 0
403 # case `getvalbyid blog mode $2` in
404 # normal|*open*|"") return 0 ;;
405 # *closed*)
406 # owner=`getvalbyid blog owner $2`
407 # if isgrp $owner; then
408 # isgrpowner $1 $owner && return 0 || return 1
409 # elif isuser $owner; then
410 # [ x"$1" = x"$owner" ] && return 0 || return 1
411 # fi
412 # esac
413 # ↑ 要はこういう処理を↓で一気にやっている
414 sql="with getblog as (\
415 select key,val from blog_s where id=(\
416 select blogid from article where id in\
417 (select id from $2 where rowid=$3))),\
418 getowner as (select val from getblog where key='owner'),\
419 getmode as (select val from getblog where key='mode')\
420 select case\
421 when (select author from article where\
422 id=(select id from $2 where rowid=$3))='$1' \
423 then 'author'\
424 when (select val from getmode) in ('report-open', 'normal')\
425 then 'open'\
426 when (select val from getmode) is null \
427 then 'open'
428 when (select val from getowner) in (select gname from grp)\
429 then (select user from grp_adm where \
430 gname=(select val from getowner) and \
431 user='$1')\
432 when (select author from article where\
433 id=(select id from $2 where rowid=$3))='$1'
434 then 'user+author'
435 else '' end;"
436 ## err isfilereadable: sql="`echo $sql`"
437 # caseのネストで内側のcaseがスカラーtrueを返しても外側はtrue扱いにならない
438 result=`query "$sql"`
439 [ -n "$result" ] && return 0
440 return 2
441 }
442 linkhome() {
443 # $1=UserOrGroup
444 echo -n '<a href="?'
445 if isuser $1; then
446 err "select 'home+'||rowid from user where name='$1';"
447 query "select 'home+'||rowid from user where name='$1';"
448 else
449 echo -n "grp+$1"
450 fi
451 echo "\">`gecos $1`</a>"
452 }
453 hreflink() {
454 # s4 specific notation:
455 # ^href=URL
456 # ^iframe=URL
457 # OSM umap Wikistyle Notation:
458 # [[URL]] - Simle Link
459 # [[URL|Word]] - Link with anchor word
460 # {{URL}} - <img src="URL">
461 # {{URL|width}} - <img src="URL" width="width">
462 # {{{URL}} } - <iframe src="URL"></iframe>
463 # {{{URL|height}} - <iframe src="URL" height="height"></iframe>
464 _hrefptn="[-A-Za-z0-9,.:;/~_%#&+?=@!]*"
465 sed -e "s|\[\[\($_hrefptn\)\|\(.*\)\]\]|<a href=\"\1\">\2</a>|g" \
466 -e "s|\[\[\($_hrefptn\)\]\]|<a href=\"\1\">\1</a>|" \
467 -e "s|{{{\($_hrefptn\)\|\(.*\)}}}|<iframe src=\"\1\" height=\"\2\"></iframe>|g" \
468 -e "s|{{{\($_hrefptn\)}}}|<iframe src=\"\1\"></iframe>|g" \
469 -e "s|{{\($_hrefptn\)\|\(.*\)}}|<img src=\"\1\" width=\"\2\">|g" \
470 -e "s|{{\($_hrefptn\)}}|<img src=\"\1\">|g"\
471 -e "s|^href=\($_hrefptn\)|<a &>\1</a>|" \
472 -e "s|^iframe=\($_hrefptn\)|<iframe src=\"\1\"></iframe>|"
473 }
474 minitbl() {
475 sed -n '
476 /^|.*|/ {; # If the line begin with "|" and has 2 or more "|"
477 s,|$,,; # Remove trailing "|" first
478 s,|\* *\([^|]*\) *,<th>\1</th>,g; # "|*..." to "<th>...</th>"
479 s,| *\([^|]*\) *,<td>\1</td>,g; # "|..." to "<td>...</td>"
480 s,^,<tr>,; s,$,</tr>,; # Enclose with "<tr>" and "</tr>"
481 H; # Concat this line to HoldSpace
482 s/.*//; # Delete PatternSpace for finalization
483 $ b cont
484 d; # If in final line, output the rest, else jump to next turn
485 }
486 :cont
487 x; # For non-"|" lines, check HoldSpace
488 /^./ {; # If HoldSpace has "|" table elements
489 s|^|<table class="mini">|; # Enclose whole elements like this:
490 # s|$|</table>|; # <table class="mini">..\n..</table>
491 p; # Print whole "table" element
492 s/.*//; # Erase all when done.
493 x; s|^|</table>|; x; # Preppend /table to the next line
494 }
495 x; # Back to the newest line
496 p; # Print rest'
497 }
498 acclog() (
499 # $1=table, $2=rowid
500 n=${2%%[!0-9]*} # Remove non-digit chars from $2(should be rowid)
501 if [ -n "$n" ]; then
502 now=`date +"%F %T"`
503 #query "replace into acclog values('$user', '$1', '$n', '$now');"
504 #query "replace into acclog values('$user', '$1', $n, '$now');"
505 query "replace into tblaccesses values('$user', '$1', $n, '$now');"
506 fi
507 )
508 gecos() (
509 u=`sqlquote ${1:-$user}`
510 query "select gecos from gecoses where name=$u;"
511 )
512 setpar() {
513 query "replace into par values('$session', '$1', '$2', \"$3\");"
514 }
515 replpar() {
516 query "update par set val=\"$3\" where sessid='$session' and var='$1' and type='$2';"
517 }
518 getpar() {
519 val=`query "select val from par where var='$1' and sessid='$session' $2;"`
520 ## err getpar/val1: "val=[$val]"
521 if [ -z "$val" ]; then
522 val=`query "select val from cookie where var='$1' and sessid='$session' $2;"`
523 fi
524 ## err getpar/val2: "val=[$val]"
525 case "$var" in
526 owner)
527 if [ x"$user" = x"$val" ]; then
528 echo $user; return
529 elif ismember $user $val; then
530 echo $val; return
531 fi ;;
532 esac
533 ## err getpar/ret: "val=[$val]"
534 echo "$val"
535 }
537 getpartype() {
538 query "select type from par where var='$1' and sessid='$session' $2;"
539 }
540 getparcount() {
541 query "select count(*) from par where var='$1' and sessid='$session' $2;"
542 }
543 getparfilename() {
544 # null if type of $1 is not file
545 (f=`query "select val from par where var='$1' and sessid='$session' and type='file' $2;"`
546 [ -n "$f" ] && echo $f)
547 }
548 sqlquote() {
549 (v="$1"
550 case "$v" in
551 "") return ;; # null
552 "X'"*) # quoted hex string
553 echo $1 ;;
554 *\"*) # string including dbl-quote"
555 v=`echo "$v"|sed -e 's/\"/\"\"/g'`
556 echo "\"$v\""
557 return ;;
558 *.*.*|*-*-*|*[Ee]*[Ee]*|[Ee]*|*[\ -,:-df-~]*) # string
559 echo "\"$v\""
560 return ;;
561 *)
562 if expr "$v" : '[-0-9.Ee][-0-9.Ee]*$' >/dev/null 2>&1; then
563 echo $v # MAYBE numeric, maybe...
564 else
565 echo "\"$v\""
566 fi ;;
567 esac)
568 }
569 sqlquotestr() (
570 case "$1" in
571 *\'*) v=`echo "$1"| sed "s/'/''/g"`
572 echo "'$v'" ;;
573 *) echo "'$1'" ;;
574 esac
575 )
576 mktempd() {
577 TMPDIR=$tmpd mktemp -d -t $session
578 }
579 getcachedir() { # $1=maintable
580 if [ -n "$imgcached" ]; then
581 echo $imgcached/$(echo ${1:-hoge}|md5)/$thumbxy
582 else
583 echo $tmpd/$thumbxy
584 fi
585 }
586 getval() {
587 # $1=table $2=col $3(optional)=condition
588 case `gettbl_coltype "/$1/$2"` in
589 user|author) # author added 2015-06-18 for article(author)
590 echo "$user" ;;
591 stamp|datetime)
592 date "+%F %T" ;;
593 serial)
594 (s=`getpar $2`
595 if [ -n "$s" ]; then echo $s; else echo "`date +%s`x$$"; fi) ;;
596 *)
597 getpar "$2" "$3";;
598 esac
599 }
601 getvalquote() {
602 # $1=table $2=col $3(optional)=condition
603 (v=`getval "$@"`
604 case "$v" in
605 "") echo NULL ;;
606 *) sqlquote "$v" ;;
607 esac)
608 }
609 getparquote() {
610 sqlquote `getpar $1`
611 }
612 getbinbyid() {
613 # $1=tbl $2=col $3=rowid $4=tmpdirForBinary
615 }
616 getvalbyid() {
617 # $1=tbl $2=col $3=rowid $4=tmpdirForBinary
618 # If two or more values found, save them to $tmpd/${column}.$N and
619 # store the number of files into $tmpd/${column}.count and
620 # their each rowid stored into $tmpd/${column}.$N.rowid.
621 ## err gtb-$1=`gettblcols $1`, tbl=$1, col=$2, '$3'=$3
623 (for c in `gettblcols $1`; do
624 if [ x"$2" = x"$c" ]; then
625 ###sq $db "select $2 from $1 where rowid=$3"
626 query "select $2 from $1 where rowid=$3;"
627 return
628 fi
629 done
630 rowid=$3
631 pk=`gettblpkey $1`
632 key=`query "select $pk from $1 where rowid=$3;"`
633 getkey="(select $pk from $1 where rowid=$3)"
634 ### err "select $pk from $1 where rowid=$3" - key=$key '$4(tmp)'=$4
635 for kt in s m; do
636 td=${4:-$tmpd}
637 [ -d $td ] || mkdir -p $td
638 t=${1}_$kt
639 for c in `gettbl_${kt}_cols $1`; do
640 vcount=1 # count(val)
641 if [ x"$2" = x"$c" ]; then
642 #### cond="$t where $pk=\"$key\" and key=\"$c\"" #2015-07-22
643 cond="$t where $pk=$getkey and key=\"$c\""
644 val=`query "select val from $cond limit 1;"`
645 type=`query "select type from $cond limit 1;"`
646 if [ $kt = m ]; then
647 ###vcount=`sq $db "select count(val) from $cond"`
648 # Reset val to store filenames if type is string
649 val=`query "select val from $cond and type like 'file:%' order by rowid;"`
650 err gvb1-sql: "select count(val) from $cond;"
651 vcount=`query "select count(val) from $cond;"`
652 echo $vcount > $td/$c.count
653 i=0
654 ## err gvbid: i=$i vcount=$vcount
655 while [ $i -lt $vcount ]; do
656 slice="order by rowid limit 1 offset $i"
657 i=$((i+1))
658 fn=$c.$i
659 err td=$td, fn=$fn, type=$type, val="[$val]"
660 case $type in
661 file:*)
662 #file=$td/$val
663 r_f=`query "select rowid||'//'||val from $cond $slice;"`
664 f_rid=${r_f%%//*}
665 file=$td/${r_f##*//}
666 # FOR SPEED: Skip file generation if imgcache exists
667 [ -s "$file" -a -s "$td/$fn.rowid" -a -s "$file.rowid" ] \
668 && [ x"$f_rid" = x"`cat $td/$fn.rowid`" ] \
669 && continue
670 # err gvbid-get="select quote(bin) from $cond $slice;"
671 ## err output: "fn=[$fn] file=[$file]"
672 sq $db<<EOF | unhexize > "$file"
673 .output '$td/$fn.rowid'
674 select rowid from $cond $slice;
675 .output '$td/$fn'
676 select val from $cond $slice;
677 .output '$td/${fn}.content-type'
678 select substr(type, 6) from $cond $slice;
679 .output stdout
680 select quote(bin) from $cond $slice;
681 EOF
682 ## err gvbid-get2: "`ls -lF $file`"
683 ## err i=$i - file=$file rowid=`cat $td/$fn.rowid`
684 cp "$td/$fn.rowid" "$file.rowid" 2>&3 # for convenience
685 cp "$file" "$file.orig" 2>&3
686 ls -lh "$file" |
687 awk '{print $5"B"}'|sed 's/BB/B/' > "$file.size"
688 case "$type" in
689 *:[Ii]mage*) mogrify -geometry $thumbxy "$file" ;;
690 ### ここのアイコンを増やしたい
691 *|*:[Aa]pplication*)
692 convert -geometry $thumbxy $imgdir/file-icon.png \
693 png:- > "$file"
694 ;;
695 esac
696 ;;
697 *)
698 sq $db<<EOF
699 .output $td/$fn.rowid
700 select rowid from $cond $slice;
701 .output $td/$fn
702 select val from $cond $slice;
703 EOF
704 val=$val${val:+$nl}"`echo $fn`" # should be delimited by newline
705 ;;
706 esac
707 done
708 else
709 rm -f $td/$c.count
710 case $type in
711 file:*)
712 echo "$val" \
713 | while read fn; do
714 file=$td/$fn
715 if [ ! -s "$file" ]; then
716 ## sq $db "select quote(bin) from $cond and val=\"$fn\"" \
717 query "select quote(bin) from $cond and val=\"$fn\";" \
718 | unhexize > "$file"
719 ##@@## -- echo ${type#file:} > "$file.content-type"
720 case $type in
721 *:[Ii]mage*) mogrify -geometry $thumbxy "$file" ;;
722 *:[Aa]pplication*)
723 convert -geometry $thumbxy $imgdir/file-icon.png \
724 png:- > $file ;;
725 esac
726 fi
727 done
728 ;;
729 esac
730 fi
731 echo "$val" # Keep newlines by ""
732 return
733 fi
734 done
735 done)
736 }
737 getvalbypkey() (
738 # $1=tbl $2=col $3=pkey $4=tmpdirForBinary
739 pk=`gettblpkey $1`
740 rowid=`query "select rowid from $1 where $pk='$3';"`
741 getvalbyid "$1" "$2" $rowid $4
742 )
743 getvalbycond() {
744 # $1=tbl $2=col $3=SQL-Condition
745 ###rowid=`sq $db "select rowid from $1 where $3"`
746 rowid=`query "select rowid from $1 where $3;"`
747 if [ -n "$rowid" ]; then
748 getvalbyid "$1" "$2" $rowid "$4"
749 fi
750 }
751 getpwfield() {
752 # getpwfield user column
753 # val=`sqlite3 $db "select $2 from passwd where name='$1' $3"`
754 val=`getvalbycond user $2 "name='$1'"`
755 if [ -n "$val" ]; then
756 echo "$val"
757 return 0
758 else
759 return 1
760 fi
761 }
762 encode() {
763 if [ -z "$sha1" ]; then
764 if type sha1 >/dev/null 2>&1; then
765 sha1=sha1
766 elif type sha1sum >/dev/null 2>&1; then
767 sha1=sha1sum
768 elif type gsha1sum >/dev/null 2>&1; then
769 sha1=gsha1sum
770 fi
771 fi
772 $sha1 "$@" | cut -d' ' -f1
773 }
774 enjpeg() {
775 if [ -z "$cjpeg" ]; then
776 if type cjpeg >/dev/null 2>&1; then
777 cjpeg="cjpeg"
778 else
779 cjpeg="convert - jpeg:-"
780 fi
781 fi
782 $cjpeg "$@"
783 }
784 mycrypt() (
785 key=$1 salt=$2
786 err \$2=$2
787 case $2 in
788 '$'*'$'*) salt=${salt#\$4\$}
789 salt=${salt%\$*} ;;
790 esac
791 echo -n '$4$'"$salt"'$'
792 echo "$salt$key" | encode || exit 1 # Abort if fail to call encode
793 )
794 hexize() {
795 if [ -z "$hexize" ]; then
796 if type xxd >/dev/null 2>&1; then
797 hexize="xxd -p"
798 else
799 hexize_hd() {
800 hexdump -ve '1/1 "%.2x"'
801 }
802 hexize="hexize_hd"
803 fi
804 fi
805 cat "$@" | $hexize | tr -d '\n'
806 }
807 unhexize() {
808 if [ -z "$unhex" ]; then
809 if type xxd >/dev/null 2>&1; then
810 unhex="xxd -p -r"
811 elif type perl >/dev/null 2>&1; then
812 cat >$tmpd/unhex.pl<<EOF
813 s/([0-9a-f]{2})/print chr hex \$1/gie
814 EOF
815 # Perl refuses -e in setuid circumstances, which can be absurdly
816 # avoided by creating scripts in a file where its parent directory is
817 # world writable...:)
818 unhex="perl -n $tmpd/unhex.pl"
819 fi
820 fi
821 cat "$@" | $unhex
822 # cat $1 | tee /tmp/uh.in| $unhex | tee /tmp/uh.out
823 }
824 percenthex() {
825 hexize "$@" | sed 's/\(..\)/%\1/g'
826 }
827 htmlescape() {
828 sed -e 's/\&/\&amp;/g' -e 's/"/\&quot;/g' -e "s/'/\&apos;/g" \
829 -e "s/</\&lt;/g; s/>/\&gt;/g"
830 }
831 enascii() {
832 if [ -z "$enascii" ]; then
833 if type kakasi >/dev/null 2>&1; then
834 enascii="kakasi -Ha -Ka -Ja -Ea -ka"
835 else
836 enascii_now=`date +%FT%T`
837 enascii_sed() {
838 nkf -Z0Z1Z2 \
839 | sed -e "s/^/$enascii_now/" -e "s|[^-0-9.A-z/,()_=]|x|g"
840 }
841 enascii="enascii_sed"
842 fi
843 fi
844 cat "$@" | $enascii
845 }
846 size_h() {
847 i="$1" oi=$1
848 set -- B B KB MB GB TB
849 while [ $((i)) -gt 9 -a -n "$1" ]; do # -gt 9 means $oi > 1024
850 oi=$i
851 i=$((i/1024))
852 shift
853 done
854 echo ${oi}$1
855 }
856 gettblconf() {
857 if [ -z "$tconfs" ]; then
858 ## tconfs=`sq $db \
859 tconfs=`query \
860 "select tbl||'/'||col||'='||keytype||'/'||objtype from $conftbl;"`
861 fi
862 # /tb1/col1=p/text /tb1/col2=s/text /tb1/col3=m/image /tb2/col1=p/text ...
863 }
864 gettblkeys() {
865 # $1=tbl
866 gettblconf
867 echo "$tconfs" | fgrep "/$1/" | \
868 (type="" keys="" fks="" cols="" scols="" mcols="" hcols=""
869 while IFS='=' read tc conf; do # tc=/tb1/col1 conf=s/text
870 col=${tc##*/} type=${conf%%/*}
871 case $type in
872 *p*)
873 cols=$cols"${cols:+:}$col"
874 keys=$keys"${keys:+:}$col" ;;
875 *f*) cols=$cols"${cols:+:}$col"
876 fks=$fks"${fks:+:}$col" ;;
877 *m*) mcols=$mcols"${mcols:+:}$col" ;;
878 *s*) scols=$scols"${scols:+:}$col" ;;
879 esac
880 case $type in
881 *h*) hcols=$hcols"${hcols:+:}$col" ;;
882 esac
883 done
884 echo "_keys=$keys _fks=$fks _cols=$cols _scols=$scols _mcols=$mcols _hcols=$hcols")
885 }
886 gettblpkey() {
887 # $1=tbl
888 gettblkeys $1 | cut -d ' ' -f 1 | sed -e 's/.*=//' -e 's/:/ /g'
889 }
890 gettblfkey() {
891 (x=`gettblkeys $1`
892 x=${x#*_fks=} # cut before "_fks=" including
893 echo ${x%% *} | tr ':' ' ')
894 }
895 gettblcols() {
896 (x=`gettblkeys $1`
897 x=${x#*_cols=} # cut before "_cols=" including
898 echo ${x%% *} | tr ':' ' ')
899 }
900 gettbl_s_cols() {
901 (x=`gettblkeys $1`
902 x=${x#*_scols=} # cut before "_scols=" including
903 echo ${x%% *} | tr ':' ' ')
904 }
905 gettbl_m_cols() {
906 (x=`gettblkeys $1`
907 x=${x#*_mcols=} # cut before "_mcols=" including
908 echo ${x%% *} | tr ':' ' ')
909 }
910 gettbl_h_cols() {
911 (x=`gettblkeys $1`
912 x=${x#*_hcols=} # cut before "_hcols=" including
913 echo ${x%% *} | tr ':' ' ')
914 }
915 gettbl_coltype() (
916 gettblconf
917 x=`echo "$tconfs"|fgrep $1=`
918 x=${x#*=} # cut before =
919 echo ${x#*/} # cut before p/ including
920 )
921 is_hidden() {
922 # $1=Tbl $2=col
923 gettblconf
924 x=`echo "$tconfs"|fgrep /$1/$2=`
925 x=${x#*=} # cut before =
926 x=${x%%/*} # cut after /
927 case $x in
928 *h*) return 0 ;;
929 *) return 1 ;;
930 esac
931 }
933 dbsetbyid() {
934 # $1=tbl $2=id $3=col $4=val/filename - &optional - $5=content-type
935 (t0=$1 t=$1 p=$2 c=$3
936 tsc=$t/$c val=$4
937 unset primary update
938 gettblconf
939 #err tsc=$tsc, tconfs="$tconfs"
940 conf=`echo "$tconfs"|fgrep "$tsc"=`
941 #err conf=$conf
942 case ${conf#*=} in
943 p*) primary=1 ;;
944 f*) update=1 ;;
945 u*) ;;
946 m*) t=${t}_m;;
947 s*) t=${t}_s;;
948 esac
949 #err t=$t
950 type=text fn=""
951 case $conf in
952 */password)
953 type=encoded ### val=`echo $val|encode`
954 ;;
955 */image*|*/document*)
956 type=`file --mime-type - < "$val" | cut -d' ' -f2`
957 bin="X'`hexize "$val"`'"
958 ;;
959 esac
960 pkey=`echo "$tconfs"|grep "${t0}/.*=p"|sed 1q`
961 pkey=${pkey#/*/} # cut $tbl/
962 pkey=${pkey%=p/*} # cut =p/... -> primary key
963 if [ "$primary" ]; then
964 nulls=`echo "$tconfs"|grep "$t/.*=[fu]/"|sed 's/^.*/, NULL/'|tr -d '\n'`
965 ###sq $db "replace into $t values(\"$val\"$nulls)"
966 query "replace into $t values(\"$val\"$nulls);"
967 elif [ "$update" ]; then
968 ###sq $db "update $1 set $c=\"$val\" where $pkey=\"$p\""
969 query "update $1 set $c=\"$val\" where $pkey=\"$p\";"
970 else
971 #err "replace into $t values(\"$p\", \"$c\", \"$type\", \"$val\", \"$bin\")"
972 ###sq $db "replace into $t values(\"$p\", \"$c\", \"$type\", \"$val\", \"$bin\")"
973 query "replace into $t values(\"$p\", \"$c\", \"$type\", \"$val\", \"$bin\");"
974 fi
975 )
976 }
977 expire() (
978 at="${1:-$timeout}"
979 FMT="${2:-%F %T}"
980 TZ=GMT gdate -d "$at" +"$FMT"
981 )
982 addsession() {
983 # expireをセット
984 # loginの先にどの画面に行くかの状態遷移表書式を決める
985 expire=`expire ${2:-"+1min"}`
986 query "replace into session values('$1', '$expire');"
987 # Remove old session parameters
988 now=`expire now`
989 query "delete from session where expire < '$now';"
990 }
991 gencookie() (
992 for kv; do
993 expire="`expire '' '%a, %d-%b-%Y %H:%M:%S GMT'`"
994 echo "Set-Cookie: $kv; expires=$expire"
995 done
996 )
997 contenttype() {
998 echo "Content-type: ${1:-text/html; charset=utf-8}"
999 contenttype() {} # Only need to work once
1001 putheader() {
1004 putfooter() {
1005 _m4 -D_TITLE_="${TITLE:-$myname}" $layout/footer.m4.html
1007 getcookie() (
1008 for kv in `echo $HTTP_COOKIE|sed 's/[;, ]/ /g'`; do
1009 k="${kv%%=*}"
1010 v="`echo ${kv#*=}|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
1011 query "replace into cookie values('$session', '$k', 'string', \"$v\");"
1012 done
1014 genrandom() {
1015 # $1=columns (default: 10)
1016 dd if=/dev/urandom count=1 2>/dev/null|nkf -MB|fold -w${1:-10}|sed -n 10p
1018 genserial() {
1019 echo $((($(date +%s)-1433084400)/10))c$$
1021 smail() {
1022 # smail rcpts subj (file)
1023 # $SMAIL_TO <- Recipient value of To: header
1024 # $MAIL_FROM <- From: header value
1025 from=`echo "${MAIL_FROM:-$admin}"|nkf -jM|tr -d '\n'`
1026 rcpt=`echo $1` # strip newlines
1027 subj=`echo $2|nkf -jM|tr -d '\n'`
1028 (_m4 -D_RCPT_="${SMAIL_TO:-$rcpt}" -D_SUBJ_="\`$subj'" -D_FROM_="$from" $msgdir/mail-header.m4
1029 cat $3 | nkf -jd ) | sendmail -f $admin $rcpt
1031 setviastring() {
1032 table=$1
1033 oifs="$IFS"
1034 IFS="&"
1035 for us in $2; do
1036 k=${us%%=*}
1037 v="`echo ${us#*=}|tr '%+' '= '|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
1038 query "replace into $table values('$session', '$k', 'string', \"$v\");"
1039 #echo $k=$v
1040 done
1041 IFS="$oifs"
1043 checkdomain() (
1044 # Check the validity of domain by referring DNS
1045 item=$1
1046 err checkdomain $1
1047 host ${item#*@} 1>&3 2>&3
1048 host ${item#*@} >/dev/null 2>&1
1050 pwcheck() {
1051 # $1=passwd
1052 dbpswd=`getpwfield $user pswd`
1053 encpswd=`mycrypt "$1" "$dbpswd"`
1054 ## err user=$user, pswd=$1, db=$dbpswd, enc=$encpswd
1055 [ x"$dbpswd" = x"$encpswd" ]
1057 mypwhash() {
1058 mycrypt `cat` `genrandom 5`
1060 wasureta() {
1061 user=$1
1062 if ! checkdomain $user; then
1063 contenttype; echo
1064 _m4 -D_TITLE_='Invalid email' $layout/title-only.html
1065 echo "ユーザ名($user)には正しいメイルアドレスが必要です。" | html p
1066 putfooter
1067 exit 0
1068 fi
1069 newpswd=`genrandom` # newsalt=`genrandom 5`
1070 #encpswd=`mycrypt "$newpswd" "$newsalt"`
1071 encpswd=`echo $newpswd|mypwhash`
1072 dbsetbyid user $user pswd "$encpswd"
1073 # Avoid $user substitution with m4, because $url comes from user input.
1074 _m4 -D_PSWD_="$newpswd" -D_URL_="$url" -D_ADMIN_="$admin" \
1075 $msgdir/mail-newaccount.m4 \
1076 | sed "s/_USER_/$user/g" \
1077 | smail $user "New Account"
1079 checkauth() {
1080 user=`getpar user`
1081 skc=`getpar skey` # from cookie
1082 [ -z "$user" ] && return 3
1083 skey="`getpwfield $user skey`"
1084 if [ -n "$skey" ]; then
1085 if [ x"$skey" = x"$skc" ]; then
1086 return 0
1087 fi
1088 fi
1089 pswd=`getpar pswd`
1090 quser=`sqlquotestr "$user"`
1091 dbuser=`query "SELECT name FROM user WHERE name=$quser;"`
1092 if [ -z "$dbuser" ]; then
1093 return 1
1094 elif [ x"$pswd" = x"wasureta" ]; then
1095 wasureta $user
1096 return 1 # wasureta error
1097 fi
1098 # dbpswd="`sq $db \"select pswd from passwd where name='$user'\"`"
1099 # putheader; echo; echo user=$user, db=$dbpswd, enc=$encpswd
1100 if pwcheck "$pswd"; then
1101 newsession=`genrandom 50`
1102 dbsetbyid user $user skey "$newsession"
1103 gencookie "user=$user" "skey=$newsession"
1104 return 0
1105 fi
1106 return 2 # Password mismatch
1108 showlogin() {
1109 args=`echo $myargs|tr ' ' '+'`
1110 _m4 -D_SYSNAME_="Welcome" -D_MYNAME_="$myname${args+?}$args" \
1111 $layout/login.m4.html
1112 exit 0
1114 dologin() {
1115 checkauth
1116 st=$?
1117 if [ $st != 0 ]; then
1118 contenttype; echo
1119 _m4 -D_USER_="$user" -D_URL_="$url" -D_ADMIN_="$admin" \
1120 $msgdir/login-fail-$st.m4.html
1121 showlogin # and EXIT
1122 fi
1125 # Do instant jobs here
1126 dbsetup
1127 trap cleanup INT HUP EXIT TERM PIPE
1128 # trap cleanup INT HUP
1130 err() {
1131 echo "$@" 1>&3
1134 cgiinit() {
1135 session=`date +%F-$$`
1136 tmpf=tmp/stream
1137 tmpd=`tmpd=$tmpdir mktempd`
1138 tmpfiles=$tmpfiles" $tmpd"
1139 addsession $session
1140 getcookie
1141 case "$REQUEST_METHOD" in
1142 get|GET) s="$QUERY_STRING" ;;
1143 post|POST) ## dd count=$CONTENT_LENGTH bs=1 of=$tmpf 2>/dev/null #slow
1144 ## dd bs=$CONTENT_LENGTH count=1 of=$tmpf # NOT working
1145 # cat > $tmpf # too much?
1146 head -c $CONTENT_LENGTH > $tmpf # safe?
1147 (echo CL=$CONTENT_LENGTH; ls -lF $tmpf) 1>&3
1148 s="`cat tmp/stream`"
1149 tmpfiles=$tmpfiles"${tmpfiles+ }$tmpf"
1150 ;;
1151 esac
1152 case "$CONTENT_TYPE" in
1153 *boundary*)
1154 bndry=${CONTENT_TYPE#*boundary=}
1155 #for us in `LC_CTYPE=C ./mpsplit.rb "$bndry" $tmpd < $tmpf`
1156 for us in `LC_CTYPE=C ./mpsplit.pl "$bndry" $tmpd < $tmpf`
1157 do
1158 k=${us%%\=*}
1159 #echo u=$us
1160 #v="`echo ${us#*=}|nkf -Ww -mQ|sed -e 's/\"/\"\"/g'`"
1161 v="`echo ${us#*=}|unhexize|sed -e 's/\"/\"\"/g'`"
1162 # err k=$k v=$v
1163 case "$k" in
1164 *:filename)
1165 type='file'; k=${k%:filename}
1166 # DO NOT ALLOW Space and '|' in file names
1167 newv=`echo "$v"|sed 's/[ \|]/X/g'`
1168 if [ x"$v" != x"$newv" ]; then
1170 fi
1171 # (echo k=$k v="[$v]"; ls -lF "$tmpd/$v"; file --mime-type "$tmpd/$v") 1>&3
1172 case `file --mime-type - < "$tmpd/$v"|cut -d' ' -f2` in
1173 [Ii]mage/x-xcf)
1174 bzip2 "$tmpd/$v"
1175 v=${v}.bz2
1176 ;;
1177 [Ii]mage/x-*|*/vnd.*) ;;
1178 [Ii]mage/*)
1179 mogrify -resize $maximagexy'>' "$tmpd/$v"
1180 ;;
1181 esac
1182 ;;
1183 *)
1184 type='string'
1185 ;;
1186 esac
1187 #sq $db "replace into par values('$session', '$k', '$type', \"$v\")"
1188 setpar "$k" "$type" "$v"
1189 done
1190 ;;
1191 *)
1192 setviastring par "$s"
1193 ;;
1194 esac
1196 email4group() {
1197 # Get for-$1=group email address(es) for $2...=users
1198 qgrp=`sqlquote "$1"`; shift
1199 users=`for i; do sqlquote "$i"; done`
1200 users=`echo $users|tr ' ' ','`
1201 sql="select coalesce(s.val, g.user) from grp_mem g
1202 left join grp_mem_s s on g.gname=s.gname and g.user=s.user
1203 and s.key='email'
1204 where g.gname=$qgrp and g.user in ($users);"
1205 query "$sql"
1207 email4groupbyuid() {
1208 # Get for-$1=group email address(es) for $2...=user-ids
1209 qgrp=`sqlquote "$1"`; shift
1210 uids=`echo "$@"`
1211 uids=`echo $uids|tr ' ' ','`
1212 sql="WITH
1213 grpemails AS (
1214 SELECT gname, user, val email
1215 FROM grp_mem NATURAL JOIN grp_mem_s
1216 WHERE key='email' AND gname=$qgrp),
1217 useremails AS (
1218 SELECT user.rowid rid, user.name, val email
1219 FROM user
1220 LEFT JOIN user_s
1221 ON user.name=user_s.name AND user_s.key='email')
1222 SELECT DISTINCT coalesce(g.email, u.name)
1223 FROM useremails u LEFT JOIN grpemails g
1224 ON u.name=g.user
1225 WHERE u.rid in ($uids);"
1226 ## err email4gByid `echo $sql`
1227 query "$sql"
1229 collectemail() (
1230 # Collect email addresses for group $1
1231 # If $TEAM is set, filter by team name
1232 # If $EXCEPT is set as username(s) delimited by comma,
1233 # remove $EXCEPT from list: ....NOT IN ($EXCEPT)
1234 for e; do
1235 if isuser "$e"; then
1236 em=`query "select val from user_m where name='$e' and key='email';"`
1237 [ -n "$em" ] && echo "$em" || echo "$e"
1238 else
1239 qgrp=`sqlquote "$e"`
1240 if [ -z "$TEAM" ]; then
1241 gmem="grp_mem"
1242 else
1243 tm=`sqlquote "$TEAM"`
1244 gmem="(SELECT gname, user FROM grp_mem_m WHERE gname='$e' AND key='team' AND val=$tm)"
1245 fi
1246 ex=${EXCEPT:+"AND g.user NOT IN ($EXCEPT)"}
1247 sql="select coalesce(s.val,um.val,g.user) from
1248 $gmem g left join grp_mem_s s
1249 on g.gname=s.gname and g.user=s.user and s.key='email'
1250 left join user_m um on g.user=um.name and um.key='email'
1251 where g.gname=$qgrp $ex;"
1252 ## err CollectEmail: `echo "$sql"`
1253 query "$sql"
1254 fi
1255 done
1257 sendinvitation() (
1258 # $1=email
1259 iss="invite-`date +%s`-$user"
1260 addsession $iss +${memoplimitdays}days # 1 week due date
1261 query "replace into par values('$iss', 'invite', 'string', \"$1\");"
1262 gecos=`gecos`
1263 name=$user"${gecos:+($gecos)}"
1264 regist="$urlbase?reg+$iss"
1265 _m4 -D_URL_="$urlbase" \
1266 -D_USER_="$name" \
1267 -D_EMAIL_="$1" \
1268 -D_REGIST_="$regist" \
1269 -D_ADMIN_="$admin" \
1270 $msgdir/mail-invite.m4 \
1271 | smail $1 "BBSへの御招待"
1272 return 0
1274 emaildomaincheck() {
1275 case "$1" in
1276 *@*@*) echo "無効なアドレスです"; return 1 ;;
1277 *@*)
1278 local=${1%@*} domain=${1#*@}
1279 if ! host $domain >/dev/null 2>&1; then
1280 echo "ドメイン($domain)が見付かりません。"
1281 return 2
1282 fi
1283 return 0
1284 ;;
1285 *) echo "正しいメイルアドレスをいれてください"; return 3 ;;
1286 esac
1288 invite() {
1289 email=`getpar email`
1290 case $email in
1291 *@*@*) repo="無効なアドレスです" ;;
1292 *@*)
1293 local=${email%@*} domain=${email#*@}
1294 if ! repo=`emaildomaincheck $email`; then
1295 repo="招待アドレスのエラー: $repo"
1296 elif [ -n "`query \"select * from user where name='$email';\"`" ]; then
1297 repo="$email さんは既に加入しています。"
1298 elif sendinvitation $email; then
1299 repo="アドレス($email)宛に案内を送信しました。"
1300 fi ;;
1301 "") repo="招待したい人のメイルアドレスを入力してください。" ;;
1302 *) repo="無効なアドレスです" ;;
1303 esac
1304 addr=`query "select val from par where sessid like 'invite-%-$user';"`
1305 if [ -n "$addr" ]; then
1306 susp="<h2>招待済みで加入待ちのアドレス</h2><pre>$addr</pre>"
1307 fi
1308 _m4 -D_TITLE_="招待" -D_REPORT_="\`$repo'" -D_ACTION_="?invite" \
1309 -D_BODYCLASS_="default" -D_SUSPENDED_="$susp" \
1310 $layout/html.m4.html $layout/invite.m4.html
1312 regist() {
1313 # $1=session-id-for-invitation
1314 _m4 -D_TITLE_="Invitation" $layout/html.m4.html
1315 if [ -z "$1" ]; then
1316 echo "bye bye" | html p
1317 reutrn
1318 fi
1319 email=`session=$1 getpar invite`
1320 if [ -z "$email" ];then
1321 cat<<EOF
1322 <p>無効な招待状チケットです。</p>
1323 <p>招待状の有効期限(1週間)が切れているか、チケット番号が異なっています。
1324 加入している人に、再度招待してもらいましょう。</p>
1325 EOF
1326 return
1327 fi
1328 echo "$email さんようこそ" | html h2
1329 query "replace into user values('$email');"
1330 # Fake login password to wasureta
1331 query "replace into par values('$session', 'pswd', 'string', 'wasureta'),
1332 ('$session', 'user', 'string', '$email');"
1333 wasureta $email
1334 echo "このアドレスに初期パスワードを送信しました。" |html p
1335 echo "新着メイルを確認してログインしてください。" |html p
1336 addsession $1 # for removal after 1 minute
1337 _m4 -D_SYSNAME_="Initial Login" -D_MYNAME_="$myname?userconf" \
1338 $layout/login.m4.html
1339 return
1341 group_safename() {
1342 # Convert $1 to safe group name
1343 echo "$1" | tr -d '"'"'",
1345 groupupdate() {
1346 gname=`getpar gname`
1347 qgname=`sqlquote $gname`
1348 if [ -n "$gname" ]; then
1349 # See ALSO same job in showgroup()
1350 newgname=`group_safename "$gname"`
1351 err newgname=$newgname
1352 if [ x"$newgname" != x"$gname" ]; then
1353 err NewGNAME: gname=$newgname
1354 gname=$newgname
1355 echo "使用禁止文字を除去し $gname としました。" | html p
1356 replpar gname string "$gname"
1357 fi
1358 # Name confliction check
1359 parow=`getpar rowid`
1360 ## err parow=$parow
1361 qgname=`sqlquote $gname` # Set again in case gname modified
1362 query "BEGIN EXCLUSIVE;"
1363 ## err "select count(gname) from grp where rowid != ${parow:-0} and gname = $qgname;"
1364 count=$(query "select count(gname) from grp where rowid != ${parow:-0} and gname = $qgname;")
1365 if [ $count -gt 0 ]; then
1366 echo "そのグループ名は既にあります。" | html p
1367 query "END;"
1368 return
1369 fi
1370 par2table $formdir/grp.def
1371 query "END TRANSACTION;"
1372 # Remove orphan
1373 : <<EOF
1374 select a.id,b.val from (select * from blog where id in
1375 (select id from blog_s where key='owner'
1376 and val not in (select name from user union select gname from grp)))
1377 a left join blog_s b on a.id=b.id and b.key='owner';
1378 EOF
1379 rm=`getpar rm` cfm=`getpar confirm`
1380 ## err groupupdate:::: after par2tbl rmcfm=$rm$cfm
1381 if [ x"$rm$cfm" = x"yesyes" ]; then
1382 if [ -z "`query \"select gname from grp where gname=$qgname;\"`" ]; then
1383 sql="delete from blog where id in
1384 (select id from blog_s where key='owner' and val=$qgname);"
1385 err rm-grp cleaning sql=`echo $sql`
1386 query "$sql";
1387 grps # When removing a group, switch to grp-list
1388 return # and return
1389 fi
1390 fi
1391 [ -z "$parow" ] && joingrp "$gname" "$user" yes "" as-admin
1392 fi
1393 sql="select rowid from grp where gname=$qgname;"
1394 grid=$(query $sql)
1395 ## err grpupdate:new-grid=$grid, sql=$sql
1396 grp $grid
1398 groupman() {
1399 note="<p>グループ名に使用できない文字は自動的に削除されます。</p>"
1401 GF_STAGE="grpconf"
1402 GF_STAGE=groupupdate
1403 DT_VIEW=grp dumptable html grp 'gname gecos:DESC mtime:TIME' 'order by b.TIME desc' \
1404 |_m4 -D_TITLE_="グループ作成" \
1405 -D_FORM_="$note`genform $formdir/grp.def`" \
1406 -D_DUMPTABLE_="syscmd(cat)" \
1407 $layout/html.m4.html $layout/form+dump.m4.html
1409 userconf() {
1410 [ -n "`getpar rowid`" ] && par2table $formdir/user.def
1411 _m4 -D_BODYCLASS_=userconf -D_TITLE_="ユーザ情報編集" $layout/html.m4.html
1412 GF_ACTION="?home" edittable "$formdir/user.def" "user" "$user"
1414 groupconf() {
1415 # $1=rowid in grp (2015-07-21 changed from gname)
1416 [ -n "`getpar rowid`" ] && par2table $formdir/grp.def
1417 _m4 -D_BODYCLASS_=groupconf -D_TITLE_="グループ情報編集" $layout/html.m4.html
1418 #rowid=`query "select rowid from grp where gname='$1';"`
1419 rowid=${1%%[!A-Z0-9a-z_]*}
1420 # GF_ACTION="?grp+$1" edittable "$formdir/grp.def" "grp" "$rowid" #2015-0804
1421 GF_STAGE="groupupdate" edittable "$formdir/grp.def" "grp" "$rowid"
1423 mems() {
1424 _m4 -D_TITLE_="参加者一覧" -D_BODYCLASS_=listmember $layout/html.m4.html
1425 kwd=`getpar kwd`
1426 listmember $kwd
1428 grps() {
1429 _m4 -D_TITLE_="グループ一覧" -D_BODYCLASS_=listgroup $layout/html.m4.html
1430 kwd=`getpar kwd`
1431 listgroup $kwd \
1432 | _m4 -D_DUMPTABLE_="syscmd(cat)" \
1433 -D_TITLE_="グループ関連操作" \
1434 -D_FORM_="<a href=\"?groupman\">新規グループ作成</a>" \
1435 $layout/form+dump.m4.html
1437 grp() { # $1=group-rowid
1438 gpg=`getpar grp`
1439 grid=${1:-$gpg}
1440 grp=`getgroupbyid "$grid"`
1441 ## . ./s4-blog.sh
1442 jg=`getpar joingrp`
1443 if [ -n "$jg" ]; then
1444 [ -n "$jg" -a -n "$grp" ] &&
1445 joingrp "$grp" "$user" "$jg" "`getpar email`"
1446 fi
1447 htmlheader=$layout/html.m4.html
1448 showgroup "$grid"
1450 sql4interestblogs() {
1451 cat<<EOF
1452 CREATE TEMPORARY VIEW interestblogs AS
1453 SELECT blog.rowid rid, id, author
1454 FROM blog
1455 NATURAL JOIN
1456 (SELECT id, val owner FROM blog_s WHERE key='owner') bs
1457 WHERE CASE WHEN (SELECT name FROM user where name=bs.owner) IS NOT NULL
1458 THEN 1 -- blog owner is an user, READABLE
1459 WHEN (SELECT user FROM grp_mem
1460 WHERE gname=bs.owner AND user='$user') IS NULL
1461 THEN 0
1462 ELSE 1
1463 END;
1464 EOF
1466 listnewblogsql() { # $1=user
1467 deftime=`query "SELECT coalesce((SELECT time FROM acclog
1468 WHERE user='$user'
1469 AND tblrowid=$blogreadflagrowid),
1470 "0");"`
1471 cat<<EOF # | tee tmp/sql.out
1472 `sql4interestblogs`
1473 WITH article_ctime as (
1474 SELECT id,blogid,author,val ctime
1475 FROM article join article_s s using(id)
1476 WHERE s.key='ctime'
1477 ), blog_title_owner as (
1478 SELECT blg.rid brid, id,
1479 max(case key when 'title' then val end) title,
1480 max(case key when 'owner' then val end) owner
1481 FROM interestblogs blg, blog_s using(id) group by id
1482 ), visited AS (
1483 SELECT b.id id, brid, b.owner owner, b.title title, ctime, ac.author author
1484 FROM blog_title_owner b, article_ctime ac
1485 JOIN acclog al
1486 ON b.id=ac.blogid
1487 AND al.tbl='blog'
1488 AND al.tblrowid=brid
1489 AND al.user='$user'
1490 AND al.time < ctime
1491 AND '$deftime' < ctime
1492 ), unvisited as (
1493 SELECT b.id id, brid, b.owner owner, b.title title, ctime, ac.author author
1494 FROM blog_title_owner b, article_ctime ac
1495 ON b.id=ac.blogid
1496 WHERE brid NOT IN (SELECT tblrowid FROM acclog
1497 WHERE tbl='blog' AND user='$user')
1498 AND ctime > '$deftime'
1499 ), blogall as (
1500 /* ---------------------------------------
1501 Collect new articles with dividing them into visited and unvisited
1502 separately, because constructing joined table of artice X acclog
1503 tends to become HUGE combinations.
1504 --------------------------------------- */
1505 SELECT * FROM visited UNION SELECT * FROM unvisited
1506 ), news as (
1507 select bl.brid brid, bl.title, bl.id blid, ctime,
1508 count(bl.id) "新着", bl.owner, bl.author
1509 from blogall bl /* left join a_u l
1510 on bl.brid=l.tblrowid */
1511 group by bl.id order by ctime desc,"新着" desc, bl.id
1512 LIMIT 10
1513 ) SELECT brid LINK, "新着",
1514 (SELECT count(*) FROM article WHERE blogid=blid) "総数",
1515 ctime, title,
1516 (SELECT gecos FROM gecoses WHERE name=author) gecos
1517 FROM news;
1518 EOF
1521 search_form() {
1522 help="(1)空白区切りの単語で本文検索
1523 (2)@YYYY-MM-DD 日付け(シェルパターン可)で日付け検索
1524 @2016-0[1-6] → 2016年1月から6月
1525 @>2016-01 @<2016-02-15 → 2016年1月から2月14日までの期間
1526 @week → 最近一週間
1527 (3)#番号 で記事ID検索
1528 (1)と(2)は組み合わせOK
1529 例: @2016-10-0[1-9] 芋煮
1530 → 2016年10月上旬でキーワード「芋煮」を含む記事検索"
1531 auth=""
1532 placeholder="全記事からの検索"
1533 case "$1" in
1534 author=*)
1535 a=`echo "${1#author=}"|htmlescape`
1536 g=`gecos ${1#author=}`
1537 auth="<input type=\"hidden\" name=\"author\" value=\"$a\">"
1538 placeholder="このユーザの書込検索"
1539 help="★★ $g さんの書き込みから検索します$nl$help"
1540 ;;
1541 esac
1542 cat<<-EOF
1543 <div class="fr">
1544 <form action="$myname">$auth
1545 <input type="text" name="kwd" value="" title="$help"
1546 placeholder=" $placeholder " width="10" accesskey="k">
1547 ${touchpanel:+<p class="help">$help</p>}
1548 <input type="hidden" name="stage" value="searchart">
1549 </form>
1550 </div>
1551 EOF
1554 imgsrc_cache() (
1555 # $1 = directory for cache'ing
1556 # $2 = table (user_m or grp_m)
1557 # $3 = keycond (was: condition for choosingowner)
1558 # $4 = size : S = Small, M = Medium, O = Original
1559 dir="$1" tbl="$2"
1560 keycond="$3"
1561 whos="$keycond AND key='profimg' AND type LIKE 'file:image%'
1562 ORDER BY rowid DESC LIMIT 1"
1563 [ -d "$dir" ] || mkdir -p "$dir"
1564 case "$4" in
1565 [Ss]) size=S ;;
1566 [Oo]) size=O ;;
1567 *) size=M ;;
1568 esac
1569 # ImageCache filename storing schema:
1570 # <table_s>.{key, val}={"profimgcache_S", "$cacheimg_S"}
1571 sql0="SELECT val || '//' || type FROM $tbl WHERE $whos;"
1572 sql1="SELECT hex(bin) FROM $tbl WHERE $whos;"
1573 valtype=`query "$sql0"`
1574 filename=${valtype%%//*}
1575 filetype=${valtype##*//file:}
1576 if [ x"$filename" = x"${filename%.*}" ]; then
1577 # If nor filename extension found, set it to image type
1578 case "$filetype" in
1579 image/*) filename=$filename.${filetype#image/} ;;
1580 esac
1581 fi
1582 cacheimg_S=$dir/S_$filename
1583 cacheimg_M=$dir/M_$filename
1584 cacheimg_O=$dir/$filename
1585 cacheimg=$dir/${size}_$filename
1586 sumfile="$dir/$filename.sum"
1587 tmpf=$tmpd/imgsrc_cache.$$
1588 sum=`query "$sql1" | tee $tmpf | encode` # encode() is maybe sha1
1589 if test -s "$sumfile" && [ x"`cat $sumfile`" = x"$sum" ]; then
1590 # if cache is fresh,
1591 echo "<img src=\"$cacheimg\">"
1592 else
1593 fifo=$tmpd/fifo; mkfifo $fifo
1594 fmt=${filename##*.}
1595 ## [[ NOTE ]]
1596 ## a. convert oldimage newimage
1597 ## b. convert oldimage fmt:- | convert - newimage
1598 ## b is much smaller than a
1599 cat $tmpf | unhexize \
1600 | tee $fifo \
1601 | convert -define ${fmt}:size=${iconxy_M}x${iconxy_M} \
1602 -resize ${iconxy_M}x${iconxy_M}'>' - ${fmt}:- \
1603 | convert - "$cacheimg_M" &
1604 cat $fifo | convert -define ${fmt}:size=${iconxy_S}x${iconxy_S} \
1605 -resize ${iconxy_S}x${iconxy_S}'>' - ${fmt}:- \
1606 | convert - "$cacheimg_S" &
1607 printf '%s' "<img src=\"data:${filetype},"
1608 cat $tmpf | sed 's/\(..\)/%\1/g'
1609 echo '">'
1610 echo "$sum" > $sumfile
1611 fi
1612 ## Now preparing cache image, done.
1613 ## Store this information to DB
1614 stbl=${tbl%_m}_s # user_s or grp_s
1615 pkey=${keycond%%=*} # Primary Key name
1616 pval=${keycond#*=} # Primary Key value
1617 query <<-EOF
1618 REPLACE INTO $stbl($pkey, key, type, val)
1619 VALUES($pval, '$iconcachekey', 'string', `sqlquote "$cacheimg_S"`);
1620 EOF
1623 showhome() {
1624 # $1=userRowIdToShow
1625 err showhome \$1=$1
1626 case "$1" in
1627 *@*) uname=`getvalbypkey user name "$1"` ;;
1628 *) uname=`getvalbyid user name $1` ;;
1629 esac
1630 ## err ShowHome: uname=$uname
1631 td=`getcachedir home/"$1"`
1632 gecos=`gecos "$uname"`
1633 ## err SH:gecos=$gecos
1634 GF_VIEWONLY=1
1635 cond="gname in (select gname from grp_mem where user='$uname')"
1636 search_form_args=""
1637 if [ x"$user" = x"$uname" ]; then
1638 conflink="<a href=\"?userconf\" accesskey=\"e\"
1639 title=\"E\">プロフィールの編集</a> /
1640 <a href=\"?blog\" accesskey=\"n\" title=\"N\">新規話題の作成</a>"
1641 # Display folders
1642 sql="select count(id) from article_m where id
1643 in (select id from article where author='$user')
1644 and type like 'file:%';"
1645 ## err nfile-sql=`echo "$sql"`
1646 nfile=`query "$sql"`
1647 # err nfile=$nfile
1648 if [ $nfile -gt 0 ]; then
1649 conflink="$conflink / <a href=\"?lsmyfile\" accesskey=\"l\"
1650 title=\"L\">過去の提出ファイル</a>"
1651 fi
1652 else
1653 search_form_args="author=$uname"
1654 fi
1655 . ./s4-blog.sh
1657 tf=$tmpd/title.$$ pf=$tmpd/profile.$$ bf=$tmpd/blogs.$$ sf=$tmpd/search.$$
1658 search_form $search_form_args > $sf
1659 echo "$gecos さん" > $tf
1660 { echo "<div class=\"noprofimg\">"
1661 viewtable $formdir/user.def user $1
1662 echo "</div>"
1663 } > $pf
1665 sqcond="WHERE name='$uname' AND key='profimg' AND type LIKE 'file:image%'"
1666 img=`query "SELECT type FROM user_m $sqcond LIMIT 1;"`
1667 imf=$tmpd/profimg.$$; touch $imf
1668 if [ -n "$img" ]; then
1669 if true; then
1670 tbl=user_m
1671 enticond="name='$uname'"
1672 imgsrc_cache "$td/main" user_m "$enticond" M
1673 else
1674 { printf '%s' "<IMG src=\"data:${img#file:},"
1675 query "SELECT hex(bin) FROM user_m $sqcond ORDER BY rowid LIMIT 1;" \
1676 | sed 's/\(..\)/%\1/g'
1677 echo '">'
1679 fi > $imf
1680 fi
1681 nblog=`query "SELECT count(id) FROM blog_s WHERE key='owner' AND \
1682 val='$uname';"`
1683 listblog $uname > $bf
1685 hometail=$tmpd/tail.$$
1686 mkfifo $hometail
1688 #Calling listgroupbytable, originally here
1691 # Display Most Recent Entry
1692 shortval=${dumpcollen:+"substr(val, 0, $dumpcollen)"}
1693 shortval=${shortval:-val}
1695 # The m.aid in the next line is suspicious. But works fine in SQLite3...
1696 DT_SQL="SELECT b.rowid || '#' || m.aid LINK,
1697 ctime,
1698 (SELECT $shortval FROM blog_s WHERE key='title' AND id=b.id) title,
1699 (SELECT gecos FROM gecoses
1700 WHERE name=(SELECT val FROM blog_s
1701 WHERE key='owner' AND id=b.id)) owner,
1702 (SELECT $shortval val FROM article_s WHERE id=m.aid AND key='text') text
1703 FROM blog b
1704 JOIN
1705 (SELECT distinct blogid, a.id aid, max(val) ctime
1706 FROM article a, article_s s
1707 ON a.id=s.id AND a.author='$uname' AND s.key='ctime'
1708 GROUP BY blogid ORDER BY val DESC LIMIT 50
1709 ) m
1710 ON b.id=m.blogid;"
1711 # This should be as follows
1712 : <<EOF
1713 WITH arts AS(
1714 SELECT (SELECT rowid FROM blog WHERE id=a.blogid) brid,
1715 a.blogid, a.id id, s.val ctime
1716 FROM article a NATURAL JOIN article_s s
1717 WHERE s.key = 'ctime' AND a.author='$user'
1718 GROUP by s.id
1720 SELECT a0.brid,a0.blogid,a0.id,a0.ctime
1721 FROM arts a0
1722 JOIN
1723 (SELECT blogid,max(ctime) mct FROM arts a1 GROUP BY blogid) a1
1724 ON a0.blogid=a1.blogid AND a0.ctime=a1.mct
1725 ORDER BY ctime DESC LIMIT 50;
1726 EOF
1728 cat<<-EOF
1729 `cgi_radio foldtabs yes 'id="mre" accesskey="d"'`<label
1730 for="mre" title="D">最近の書き込み先</label>
1731 <div class="lcto">
1732 `DT_VIEW=replyblog dumptable html blog`
1733 </div>
1734 EOF
1735 unset DT_SQL
1736 if [ x"$user" = x"$uname" ]; then
1737 # Display NEWS
1738 # 2016-06-26
1739 if [ x"`getpar readchk``getpar read`" = x"yesyes" ]; then
1740 acclog blog $blogreadflagrowid
1741 # echo "全部既読にしました" | html p
1742 fi
1743 # 2016-02-19 Counting NEWS without using dumptable.
1744 sql=`listnewblogsql "$user"`
1745 new10=`DT_SQL="$sql" DT_VIEW=replyblog dumptable html blog`
1746 cont=`echo "$new10"|grep "^<TR>"|wc -l`
1747 cont=$((cont-1))
1748 err newcount=$cont
1749 if [ $cont -gt 0 ]; then
1750 #echo "全体の新着記事${cont}傑" | html h2
1751 cgi_radio foldtabs yes 'id="new10" accesskey="f"'
1752 echo "<label for=\"new10\" title=\"X\">新着${cont}傑</label><div>"
1753 cat<<-EOF | html form 'action="?home"'
1754 `cgi_checkbox readchk yes 'id="read"'`<label
1755 for="read">新着ふくめて全部読んだことにする</label>
1756 `cgi_submit '確定'`
1757 `cgi_hidden read yes`
1758 EOF
1759 echo "$new10 <!-- new10 -->"
1760 echo "</div>"
1761 fi
1762 else # Not My Home ($user != $uname)
1763 : # DT_SQL=
1764 fi
1765 ) > $hometail & # Is background call safe to m4??
1767 listgroupbytable $formdir/grp.def $cond |
1768 _m4 -D_BODYCLASS_=home -D_TITLE_="spaste(\`$tf')" \
1769 -D_PROFILE_="spaste(\`$pf')$conflink" \
1770 -D_PROFIMG_="spaste(\`$imf')" \
1771 -D_BLOGS_="spaste(\`$bf')" \
1772 -D_SEARCH_="spaste(\`$sf')" \
1773 -D_NBLOG_="$nblog" \
1774 -D_GROUPS_="syscmd(\`cat')" \
1775 -D_HOMETAIL_="syscmd(\`cat $hometail')" \
1776 $layout/html.m4.html $layout/home.m4.html
1778 # Record access log
1779 [ -n "$1" ] && [ x"$1" != x"$user" ] && acclog user $1
1781 commission() { # $1=grp-rowid $2=user-rowid
1782 contenttype; echo
1783 ## err commission: "$@"
1784 gname=`getgroupbyid $1`
1785 echo "グループ $gname 管理者委任" \
1786 | _m4 -D_TITLE_="syscmd(\`cat')" $layout/html.m4.html
1787 if [ -n "$2" ]; then
1788 grp_reg_adm "$@"
1789 else
1790 echo "無効な指定です。普通のアクセスならここに来ないはず。"|html p
1791 fi
1793 listgroupbytable() {
1794 # $1=deffile $2...=condition
1795 tagline=`grep :tag: $1`; shift
1796 and="${1:+and }" where=${1:+where }
1797 href="<a href=\"$myname?grp+"
1798 echo '<div class="listgroup">'
1799 NGsql="select distinct tag from\
1800 (select gname, max(case key when 'tag' then val end) as tag, \
1801 max(case key when 'ctime' then val end) as ctime\
1802 from grp_s group by gname order by ctime);"
1803 sql="select val from grp_s where key='tag' $and$* group by val;"
1804 ## err ListGRP: query sql="$sql"
1805 for tag in `query "$sql"`
1806 do
1807 ## err ListGrp: tag=$tag
1808 tn=${tagline%%=${tag}*}
1809 tn=${tn##*[ :]}
1810 sql="select rowid||':'||gname as 'グループ名',説明 from
1811 (select (select rowid from grp g where g.gname=grp_s.gname)
1812 as rowid,
1813 gname,
1814 max(case key when 'gecos' then val end) as '説明',
1815 max(case key when 'tag' then val end) as 'tag',
1816 max(case key when 'mtime' then val end) as mtime from grp_s
1817 $where$* group by gname having tag='$tag' order by mtime desc);"
1818 ## err PersonalGroupList= `echo $sql`
1819 echo "<h2>$tn</h2>"
1820 echo '<table class="b listgroup">'
1821 sq -header -html $db "$sql" \
1822 | sed "s,\(<TR><TD>\)\([0-9]*\):\([^ ]*\)</TD>,\1$href\2\">\3</a>,"
1823 echo '</table>'
1824 done
1825 echo '</div>'
1827 iconhref() (
1828 # $1=icon-file, $2=Href $3=title $4...=anchor
1829 data=`percenthex "$1"`
1830 ct=`file --mime-type - < "$1"|cut -d' ' -f2`
1831 ## err iconhref: \$1=$1 \$2=$2 \$3="$@"
1832 href=$2; title=$3; shift 3
1833 echo "<a href=\"$href\"><img title=\"$title\" src=\"data:$ct,$data\">$@</a>"
1835 iconhref2() (
1836 # $1=icon-file, $2=Href $3=title $4...=anchor
1837 src=$1
1838 href=$2; title=$3; shift 3
1839 echo "<a href=\"$href\"><img title=\"$title\" src=\"$src\">$@</a>"
1841 listentry() (
1842 # $1=user/group $2=SearchKeyword $3=condition(if any) $4=grprowid(if in grp)
1843 # Referring variable $iamowner=$grp to attach owner-request links
1844 ## err listentry: \$1=$1 \$2=$2 \$3=$3
1845 cond='' hiddens=''
1846 offset=`getpar offset`
1847 offset=${offset%%[!0-9]*}
1848 offset=$((offset + 0)) # change to numeric forcibly
1849 [ $offset -lt 0 ] && offset=0
1850 limit=30
1851 dir=`getcachedir "$1"`
1852 if [ x"$1" = x"user" ]; then
1853 hrb="$myname?home"
1854 deficon=person-default.png
1855 entity="ユーザ" tbl=user link=rowid nm=name # stage=mems
1856 [ -n "$4" ] && hiddens=`cgi_hidden grid $4`
1857 gcs=gecos
1858 else # if group
1859 hrb="$myname?grp"
1860 deficon=group-default.png
1861 entity="グループ" tbl=grp link=rowid nm=gname stage=grps
1862 gcs=name
1863 tagline=`grep :tag: $formdir/grp.def|cut -d: -f5-`
1864 if [ -n "$tagline" ]; then
1865 tagconv=`echo $tagline|sed 's/\([^= :]*\)=\([^= :]*\)/-D\2=\1/g'`
1866 ## err tagconv=$tagconv
1867 fi
1868 fi
1869 if [ ! -d $dir ]; then
1870 mkdir -p $dir
1871 fi
1872 if [ ! -s $dir/$deficon ]; then
1873 convert -geometry $thumbxy $imgdir/$deficon $dir/$deficon
1874 fi
1875 if [ -n "$2" ]; then
1876 cond="where nick like '%$2%' or b.name like '%$2%'"
1877 fi
1879 # XX: これ複雑すぎるかな。もっとシンプルにしたい。$3条件も。2015-07-08
1880 # grpは呼出し元の動的スコープ変数でよくないな...
1881 ##qgrp=`sqlquote $grp`
1882 getgrp="(select gname from grp where rowid=${rowid:--1})"
1883 sql="select a.rowid, a.$link,
1884 coalesce(b.$gcs, a.$nm) as nick,
1885 coalesce(b.gecos, a.$nm) /* If group, concat (Nusers) */
1886 || case when a.$nm in (select gname from grp)
1887 then printf('(%d名)',
1888 (select count(user) from grp_mem where gname=a.$nm))
1889 else ' <'||a.$nm||'>'
1890 end
1891 as name,
1892 b.tag,
1893 case when a.$nm in (select user from grp_adm
1894 where gname=$getgrp) then '(管理者)'
1895 when '$user' in (select user from grp_adm where gname=a.$nm)
1896 then '(ADMIN)'
1897 when '$user' in (select user from grp_mem where gname=a.$nm)
1898 then '(Member)'
1899 when '$iamowner' = '' then ''
1900 else ',not='||a.rowid end as ownerlink,
1901 CASE '$entity'
1902 WHEN 'グループ'
1903 THEN coalesce(
1904 (SELECT val FROM grp_s WHERE gname=a.$nm AND key='regmode'),
1905 'open')
1906 ||
1907 CASE WHEN '$user'
1908 IN (SELECT user FROM grp_mem WHERE gname=a.$nm)
1909 THEN ' ismember'
1910 ELSE ''
1911 END
1912 ELSE 'user'
1913 END regmode
1914 from $tbl a left join
1915 (select $nm as name,
1916 max(case key when 'gecos' then val end) as gecos,
1917 max(case key when 'tag' then val end) as tag,
1918 max(case key when 'mtime' then val end) as mtime
1919 from ${tbl}_s group by $nm)
1920 b on a.$nm=b.name $cond $3
1921 order by b.tag desc, b.mtime desc, a.rowid asc"
1922 # Give precedence to newer maintained groups (2016-09-24)
1923 # Note that mtime is stored only in grp_s.
1924 ## err LE:sql.1="$sql"
1925 total=`query "with x as ($sql) select count(*) from x;"`
1926 echo "${entity} 一覧" | html h2
1927 if [ $total -gt $limit ]; then
1928 echo '<div class="right">'
1929 cgi_form $stage <<EOF
1930 <label>次の語を含む${entity}で検索:
1931 `cgi_text kwd $kwd`</label>
1932 EOF
1933 echo '</div>'
1934 fi
1935 hiddens="$hiddens
1936 `cgi_hidden kwd \"$kwd\"`
1937 `cgi_hidden stage \"$stage\"`"
1938 cat<<EOF
1939 <p>${total}件中の$((offset+1))件めから${kwd:+" - 検索語: $kwd"}</p>
1940 EOF
1941 if [ $((offset+limit)) -lt $total ]; then
1942 nextbtn=$(
1943 cat<<EOF
1944 <div class="right clear"><form action="$myname" method="POST">
1945 `cgi_submit 次の${limit}件`
1946 $hiddens
1947 `cgi_hidden offset $((offset + limit))`</form></div>
1948 EOF
1950 fi
1951 if [ $offset -gt 0 ]; then
1952 prevbtn=$(
1953 cat<<EOF
1954 <form action="$myname" method="POST">
1955 `cgi_submit 前の${limit}件`
1956 $hiddens
1957 `cgi_hidden offset $((offset - limit))`</form>
1958 EOF
1960 fi
1961 pnbtn="$nextbtn$prevbtn"
1962 echo $pnbtn
1964 ## err ListEntry: `echo "$sql"\;`
1965 query "$sql limit $limit ${offset:+offset $offset};" \
1966 | while IFS='|' read id lnk name gecos tag ownerp type; do
1967 err name=$name owner=$ownerp lnk=$lnk
1968 err newlnk=$lnk regmode=$regmode
1969 icondir=$dir/$id
1970 files=`getvalbyid $tbl profimg $id $icondir`
1971 # Pick up only last icon
1972 echo "<div class=\"iconlist xy$thumbxy $type\">
1973 <p class=\"tag _$tag\">$tag</p>" \
1974 | _m4 $tagconv
1975 if [ -n "$files" ]; then
1976 icon=`echo "$files"|tail -1`
1977 iconhref2 "$icondir/$icon" "$hrb+$lnk" "$gecos"
1978 else
1979 iconhref "$dir/$deficon" "$hrb+$lnk" "$gecos"
1980 fi
1981 echo "<br>$name${ownerp:+<br>$ownerp}"
1982 echo "</div>"
1983 done
1984 echo ${pnbtn:+"<hr>$nextbtn$prevbtn"}
1986 listmember() {
1987 listentry user "$@"
1989 listgroup() {
1990 listentry group "$@"
1992 hexteams() { # $1=gname, $2(optional)=user
1993 cond=${2:+" AND user='$2'"}
1994 query "SELECT DISTINCT hex(val) FROM grp_mem_m
1995 WHERE gname='$1' AND key='team'$cond;"
1997 showgroup() { # $1=group-rowid
1998 if [ -z "$1" ]; then
1999 grid=`getpar grid`
2000 grid=${grid%%[!0-9]*}
2001 [ -n "$grid" ] && grp=`getgroupbyid $grid`
2002 else
2003 grid=$1
2004 fi
2005 grp=`getgroupbyid $grid`
2006 qgrp=`sqlquote $grp`
2007 ## err showgroup2: grp=$grp qgrp="[$(sqlquote $grp)]"
2008 if isgroup "$grp"; then
2009 tf=$tmpd/title.$$
2010 bodyclass=`query "SELECT val FROM grp_s
2011 WHERE gname=$qgrp AND key='regmode';"`
2012 if ismember "$user" "$grp"; then
2013 ismember="ismember"
2014 qgrp=`sqlquote $grp`
2015 bodyclass="$bodyclass${bodyclass:+ }ismember"
2016 else
2017 ismember="" # bodyclass="group"
2018 fi
2019 bodyclass="$bodyclass grouphome"
2020 echo "グループ $grp" > $tf
2021 showgroupsub $formdir/grp.def "$grid" | \
2022 _m4 -D_TITLE_="spaste(\`$tf')" \
2023 -D_FORM_="syscmd(\`cat')" \
2024 -D_BODYCLASS_="$bodyclass" \
2025 -D_DUMPTABLE_="" \
2026 $htmlheader $layout/form+dump.m4.html
2027 # $htmlheader is defined in grp()
2028 else # if $grp is removed at par2table
2029 listgroup
2030 fi
2032 showgroupsub() {
2033 # $1=def-file $2=group-rowid
2034 # Using $ismember
2035 rowid=$2
2036 grp=`getgroupbyid $2`
2037 qgrp=`sqlquote $grp`
2038 td=`getcachedir grp/"$2"`
2039 #rowid=`sq $db "select rowid from grp where gname=$qgrp"`
2040 if [ -z "$rowid" ]; then
2041 #rowid=`sq $db "select rowid from grp where rowid=$grp"`
2042 #grp=`sq $db "select gname from grp where rowid=$grp"`
2043 echo "showgroupsub: invalid argument($1 $2)" | html p
2044 return
2045 fi
2046 val=`getvalbyid grp profimg $rowid $tmpd`
2047 enticond="gname=$qgrp"
2048 img=`query "SELECT type FROM grp_m WHERE $enticond LIMIT 1;"`
2049 if [ -n "$img" ]; then
2050 cat<<-EOF
2051 <p class="groupimg">
2052 `imgsrc_cache $td/main grp_m "$enticond" M`</p>
2053 EOF
2054 fi
2055 echo "<div class=\"noprofimg\">"
2056 viewtable $1 grp $rowid
2057 echo "</div>"
2058 if isgrpowner "$user" "$grp"; then
2059 echo "<p><a href=\"?groupconf+$rowid\">グループ情報の編集</a>"
2060 iamowner=$rowid
2061 fi
2062 if [ -n "$ismember" ]; then
2063 echo "${iamowner:+ / }<a href=\"?blog+$rowid\">グループの新規話題作成</a>"
2064 echo "/ <a href=\"?grpaction+$rowid\">メンバーを個別選択しての操作</a></p>"
2065 # div.fold input[type="checkbox"]:checked ~ div {display: block;}
2066 cat<<EOF
2067 <form action="?send2mem" method="POST" enctype="multipart/form-data">
2068 <div class="fold clear">
2069 `cgi_checkbox send yes id="send"`<label
2070 for="send">グループ全員にメッセージ送信</label>
2071 <div>
2072 `cgi_textarea message "" "cols=60"`
2073 `cgi_submit 送信`
2074 `cgi_reset リセット`
2075 </div>
2076 `cgi_hidden grp $rowid`
2077 </div></form>
2078 EOF
2079 fi
2080 # 加入ボタン + 加入者リスト
2081 if [ -n "$ismember" ]; then
2082 ismem='checked' state="(参加中)"
2083 else
2084 nomem='checked' state="(現在非加入)"
2085 fi
2086 # このグループでの加入アドレス
2087 eml=`query "select val from grp_mem_s where gname=$qgrp and user='$user' \
2088 and key='email';"`
2089 ##err EML: "select val from grp_mem_s where gname='$2' and user='$user' \
2090 ## and key='email';"
2091 ##err email=$eml
2092 cat <<EOF
2093 <div class="fold clear">
2094 `cgi_checkbox reg yes id="reg"`<label
2095 for="reg">自身の加入状態を操作する</label>$state
2096 <div>
2097 EOF
2098 cgi_form grp <<EOF
2099 <p>このグループに</p>
2100 <table class="b">
2101 <tr><th>メンバーとして</th><td>
2102 <label>`cgi_radio joingrp "yes" $ismem`参加</label> /
2103 <label>`cgi_radio joingrp "no" $nomem`参加しない</label></td></tr>
2104 <tr><th>参加する場合のメイルアドレス<br>
2105 <small>(メインのアドレスとは違うものにする場合に記入<br>
2106 同じでよい場合は空欄に)</small></th>
2107 <td>`cgi_text email $eml`</td></tr>
2108 </table>
2109 `cgi_hidden grp $rowid`
2110 EOF
2111 if [ x`getgroupattr $grp regmode` = x'moderated' -a -z "$ismem" ]; then
2112 echo "moderated (承認加入の)グループなので実際に参加できるのは
2113 グループ管理者が承認操作をした後になります。" | html p 'class="warn"'
2114 fi
2115 echo '</div></div>'
2116 echo '<h2>話題一覧</h2>'
2117 cgi_form searchart<<EOF
2118 <label>`cgi_text kwd`という語を含むコメントを検索</label>
2119 `cgi_hidden owner $grp`
2120 EOF
2121 cond="where a.id in (select id from blog_s where key='owner' and val=$qgrp) order by ctime desc"
2122 DT_CHLD=article:blogid \
2123 DT_VIEW=replyblog dumptable html blog 'ctime team title heading mode' "$cond"
2125 getgname="(select gname from grp where rowid=$rowid)"
2126 c="group by a.name having a.name in (select user from grp_mem where gname=$getgname)"
2127 cm="?commission+$rowid"
2128 thumbxy=50x50 listmember "" "$c" "$rowid" \
2129 |sed -e "s|\(<br>\),not=\(.*\)|\1|" # 間違って押しやすい
2130 # team list
2131 hexteams=`hexteams "$grp"`
2132 if [ -n "$hexteams" ]; then
2133 echo "チーム一覧" | html h2
2134 echo '<div class="dumptable"><table class="b">'
2135 sq $db -html -header<<-EOF
2136 SELECT val TEAM,
2137 group_concat((SELECT gecos FROM gecoses WHERE name=user), ',')
2138 MEMBERS
2139 FROM grp_mem_m WHERE gname=$qgrp AND key='team' GROUP BY val;
2140 EOF
2141 echo '</table></div>'
2142 fi
2144 grp_getbodyclass() {
2145 # Get css class name for document.
2146 # `moderated' for moderated groups
2147 # `ismember' for groups where user belongs
2148 # $1=GroupName (w/o quote)
2149 # $user=userNameCurrentlyLogin
2150 ## err grp_getbodyclass: 1="$1"
2151 qgrp=`sqlquote "$1"`
2152 query<<-EOF
2153 SELECT coalesce(
2154 (SELECT val FROM grp_s WHERE gname=$qgrp AND key='regmode'),
2155 'open')
2156 ||
2157 CASE WHEN '$user'
2158 IN (SELECT user FROM grp_mem WHERE gname=$qgrp)
2159 THEN ' ismember'
2160 ELSE ''
2161 END;
2162 EOF
2164 grpaction() { # $1=group-rowid
2165 err GRP_ACTION:IN
2166 grid=${1:-`getpar grp`}
2167 grp=`getgroupbyid "$grid"`
2168 if [ -z "$grp" ]; then
2169 echo "無効な指定です。" | html p; return
2170 fi
2171 if ! ismember $user $grp; then
2172 echo "加入者のみに許可された操作です。" | html p; return
2173 fi
2174 echo "グループ $grp 個別選択操作" \
2175 | _m4 -D_TITLE_="syscmd(\`cat')" \
2176 -D_BODYCLASS_="`grp_getbodyclass \"$grp\"`" \
2177 $layout/html.m4.html
2179 isowner=""
2180 isgrpowner "$user" "$grp" && isowner="yes"
2181 usel=`getpar usel`
2182 if [ -n "$usel" ]; then
2183 uids=$(echo `echo $usel`|tr ' ' ',')
2184 ## err grpaction-1: grp=$grp, `echo $sql`
2185 text=`getpar text`
2187 rm=`getpar rm` cfm=`getpar confirm`
2188 ## err rm=$rm cfm=$cfm
2189 if [ x"$rm" = x"yes" ]; then
2190 if [ "$isowner" ]; then
2191 if [ x"$rm$cfm" = x"yesyes" ]; then
2192 # Eliminate
2193 cond="where gname=(select gname from grp where rowid=$grid) and user in (select name from user where rowid in ($uids))"
2194 for tbl in grp_mem grp_mem_s grp_mem_m; do
2195 sql="delete from $tbl $cond;"
2196 # echo "sql=$sql"
2197 query "$sql"
2198 err rmGRPuser "$sql"
2199 done
2200 num=`query "select count(*) from user where rowid in ($uids);"`
2201 #err num=$num
2202 if [ 0$num -gt 0 ]; then
2203 sql="select coalesce(b.val,a.name) from user a left join \
2204 user_s b on a.name=b.name and key='gecos' where a.rowid in ($uids);"
2205 # err `echo "$sql"`
2206 html pre<<EOF
2207 以下の${num}名のグループ $grp 登録を解除しました。
2208 `query "$sql"`
2209 EOF
2210 fi
2211 else
2212 echo "確認のチェックがないのでやめておきます。" | html p
2213 return
2214 fi
2215 else # not Group Owner
2216 echo "グループ管理者でないのでメンバー操作はできません。" | html p
2217 return
2218 fi
2219 cat<<EOF
2221 EOF
2222 elif [ x"$rm" = x"send" ]; then # if sendmsg mode
2223 if [ -z "$text" ]; then # if msg is empty
2224 echo "なにかメッセージを..." | html p
2225 return 0
2226 fi
2227 gecos=`gecos $user`
2228 mkfrom=`getpar mkfrom`
2229 if [ x"$mkfrom" = x"yes" ]; then
2230 safegc=`echo "$gecos" | tr -d '<>@'`
2231 myuid=`query "SELECT rowid FROM user WHERE name='$user';"`
2232 fromad=`email4groupbyuid "$grp" "$myuid"`
2233 mail_from="$safegc <$fromad>"
2234 else
2235 mail_from="$admin"
2236 fi
2237 MAIL_FROM=$mail_from \
2238 smail "`email4groupbyuid "$grp" $usel` $user" \
2239 "$gecos さんからのメッセージ" <<EOF
2240 $url
2241 のグループ「$grp」のメンバーである $gecos さんから、
2242 あなた宛へのメッセージです。
2243 ----------------------------------------------------------
2244 $text
2245 EOF
2246 if [ $? = 0 ]; then
2247 echo "Note: 以下のメンバーにメッセージを送信しました。" | html p
2248 sql="select coalesce(b.val, a.name) from
2249 (select name from user where rowid in ($uids)) a
2250 left join user_s b on a.name=b.name and b.key='gecos';"
2251 html pre<<EOF
2252 `query "$sql"`
2253 (送信者である $gecos さんも含まれます)
2254 EOF
2255 err SendDone: `echo $sql`
2256 fi
2257 elif [ x"$rm" = x"commission" ]; then
2258 grp_reg_adm $grid $usel
2259 elif [ x"$rm" = x"addteam" ]; then
2260 team=`getpar team|sed "s/'/''/g"` # for single quotation
2261 newteam=`echo "$team"|tr -d ,`
2262 if [ x"$team" != x"$newteam" ]; then
2263 echo "チーム名に使えない文字を除去しました" | html p
2264 team=newteam
2265 fi
2266 if [ -z "$team" -o x"$team" = x"なし" ]; then
2267 cat<<-EOF | html p
2268 有効なチーム名を入力してください。
2269 カンマだけ、「なし」という名前は使えません。
2270 EOF
2271 echo "有効なチーム名を入力してください。" | html p
2272 else
2273 grp_add_team $grid "$team" $usel
2274 fi
2275 elif [ x"$rm" = x"rmteam" ]; then
2276 if [ x"yes" = x"`getpar teamconfirm`" ]; then
2277 rmteam=`getpar rmteam|sed "s/'/''/g"`
2278 if [ -n "`query \"SELECT val FROM grp_mem_m WHERE\
2279 gname='$grp' AND user='$user' AND key='team'\
2280 AND val='$rmteam';\"`" ]; then
2281 grp_rm_team $grid "$rmteam" $usel
2282 else
2283 echo "所属していないチームの除去操作はできません。"|html p
2284 fi
2285 else
2286 echo "確認チェックなしなのでチーム除去しませんでした。"|html p
2287 fi
2288 fi
2289 fi
2290 # New entry
2291 sql="select /* Ahh, ugly SQL, I wanna fix... */
2292 case
2293 when (select user from grp_adm where
2294 gname=(select gname from grp where rowid=$grid)
2295 and user=a.name) is not null
2296 then 'k'
2297 else ''
2298 end || a.rowid||
2299 ','||a.gecos as NAME,
2300 (SELECT count(author) /* Put post count for scoring 2016-08-01 */
2301 FROM article NATURAL JOIN article_s
2302 WHERE blogid IN
2303 (SELECT id FROM blog_s
2304 WHERE key='owner'
2305 AND val=(SELECT gname FROM grp where rowid=$grid))
2306 AND author=a.name AND key='text')
2307 as POST,
2308 (SELECT group_concat(val, ',')
2309 FROM grp_mem_m
2310 WHERE gname='$grp' AND user=a.name AND key='team') as TEAM
2311 FROM
2312 gecoses a
2313 WHERE name in (select user from grp_mem where
2314 gname=(select gname from grp where rowid=$grid))
2315 ORDER by a.gecos;"
2316 ## err grpaction: "`echo \"$sql\"`"
2317 tf=$tmpd/title.$$
2318 echo "グループ[<a href=\"?grp+$grid\">$grp</a>]参加メンバーに対する操作" > $tf
2319 cmmsg="`cgi_radio rm commission id=\"cmadmin\"`<label
2320 for=\"cmadmin\">グループ管理者委任</label>
2321 <div><p>このグループでの全権を付与します。信頼できる人に託してください。
2322 </p></div>"
2323 excmsg="`cgi_radio rm yes id=\"conf\"`<label
2324 for=\"conf\">グループ登録解除</label>
2325 <div>本当に消します! `cgi_checkbox confirm yes` 確認
2326 <p>この操作による通知は本人に行きません。
2327 あらかじめ通知するか、登録解除してよい状況かしっかり確認してください。</p>
2328 </div>"
2329 # Get team list to which current user belongs into $hexteams
2330 myhexteams=$(hexteams "$grp" "$user")
2331 allhexteams=$(hexteams "$grp")
2332 if [ -n "$myhexteams" ]; then
2333 rmteammsg="`cgi_radio rm rmteam 'id=\"cmrmteam\"'`<label
2334 for=\"cmrmteam\">チーム属性除去</label>
2335 <div>チーム属性:`cgi_select_h rmteam \"2d2d2d\" $myhexteams`
2336 を除去します: `cgi_checkbox teamconfirm yes` 確認
2337 <p>この操作による通知は本人に行きません。
2338 あらかじめ通知するか、登録解除してよい状況かしっかり確認してください。</p>
2339 </div><!-- end of $rmteammsg -->
2341 fi
2342 b1='<label> <input type="checkbox" name="usel" value="'
2343 ba='<label class="admin"><input type="checkbox" name="usel" value="'
2344 #b2='"> <span>' b3='</span></label>'
2345 # | sed -e "s|^\(<TR><TD>\)k\([0-9]*\),\([^<]*\)|\1$ba\2$b2\3$b3|" \
2346 # -e "s|^\(<TR><TD>\)\([0-9]*\),\([^<]*\)|\1$b1\2$b2\3$b3|" \
2347 lnk='"> <span>\3</span></label> [<a href="?home+\2">HOME</a>]'
2348 cgi_form grpaction<<EOF \
2349 | sed -e "s|^\(<TR><TD>\)k\([0-9]*\),\([^<]*\)|\1$ba\2$lnk|" \
2350 -e "s|^\(<TR><TD>\)\([0-9]*\),\([^<]*\)|\1$b1\2$lnk|" \
2351 | _m4 -D_TITLE_="spaste(\`$tf')" \
2352 -D_SUBTITLE_="チェック後操作ボタン" \
2353 -D_FORM_="syscmd(cat)" -D_DUMPTABLE_="" \
2354 $layout/form+dump.m4.html
2355 <p>下でチェックした人を対象として:</p>
2356 <div class="foldtabs">
2357 `cgi_radio rm addteam 'id="cmteam"'`<label
2358 for="cmteam">同じチーム属性を付与</label>
2359 <div>チーム名:`cgi_text team "" 'id="inteam" list="teams"'`
2360 `cgi_datalist_h teams $allhexteams`
2361 </div>
2362 ${rmteammsg}
2363 `cgi_radio rm send id="sendmsg"`<label
2364 for="sendmsg">メッセージ送信</label>
2365 <div>
2366 `cgi_checkbox mkfrom yes 'id="mkfrom" checked'`<label for="mkfrom"
2367 >差出人を自分に(チェックを外すと相手が返事できない)</label><br>
2368 `cgi_textarea text "" cols=40`
2369 </div>
2370 ${isowner:+$cmmsg$excmsg}
2371 `cgi_radio rm close id="x"`<label for="x">×</label>
2372 </div>
2373 <h4>$grp 参加者一覧</h4>
2374 <table class="td2r">
2375 `sq $db -header -html "$sql"`
2376 </table>
2377 `cgi_hidden grp $grid`
2378 EOF
2380 crview4article() { # $1=rowid of blog, $2(optional)=extra SQL
2381 # Create TEMPORARY VIEW
2382 query<<EOF
2383 CREATE TEMPORARY VIEW writeusers AS
2384 SELECT DISTINCT author FROM article
2385 WHERE id in (
2386 select id from article where blogid=(select id from blog where rowid=$1)
2387 );
2388 CREATE TEMPORARY VIEW movablegroups AS
2389 SELECT g.rowid growid , g.gname
2390 FROM (SELECT grp.rowid, grp.gname FROM grp JOIN grp_mem gm
2391 ON grp.gname=gm.gname -- そのユーザが属している
2392 AND user='$user') g -- グループに絞る
2393 WHERE (SELECT author FROM writeusers
2394 EXCEPT
2395 SELECT user FROM grp_mem gm WHERE gm.gname = g.gname)
2396 IS NULL;
2397 $2
2398 EOF
2400 sql4readableblogs() {
2401 # Create view of blogs that can be readable to $user
2402 # Blog is readable when:
2403 # 1: blog owner is an user
2404 # 2: else, 2.1: owner-group where the $user belongs
2405 # 2.2: else, owner-group is not moderated
2406 # blog(id, author), blog_s(id, key='owner', val= ->owner)
2407 cat<<EOF ## | tee tmp/sql.out
2408 CREATE TEMPORARY VIEW readableblogs AS
2409 SELECT blog.rowid rid, id, author
2410 FROM blog
2411 NATURAL JOIN
2412 (SELECT id, val owner FROM blog_s WHERE key='owner') bs
2413 WHERE CASE WHEN (SELECT name FROM user where name=bs.owner) IS NOT NULL
2414 THEN 1 -- blog owner is an user, READABLE
2415 WHEN (SELECT val FROM grp_s
2416 WHERE gname=bs.owner AND key='regmode') = 'moderated'
2417 AND
2418 (SELECT user FROM grp_mem
2419 WHERE gname=bs.owner AND user='$user') IS NULL
2420 THEN 0
2421 ELSE 1
2422 END;
2423 EOF
2425 editheading() { # $1=rowid-of-heading
2426 rowid=${1%%[!A-Z0-9a-z_]*}
2427 if [ -z "$rowid" ]; then
2428 echo "話題番号が未指定です。" | html p
2429 return
2430 fi
2431 owner=`getvalbyid blog owner $rowid`
2432 title=`getvalbyid blog title $rowid`
2433 GF_ACTION="?blog" edittable $formdir/blog.def blog $rowid \
2434 | _m4 -D_TITLE_="修正" \
2435 -D_SUBTITLE_="[$title]@$owner" -D_DIARY_="" \
2436 -D_BLOGS_="" -D_DUMPTABLE_="" \
2437 -D_FORM_="syscmd(\`cat')" \
2438 $layout/html.m4.html $layout/form+dump.m4.html
2439 # Move to group
2440 if isuser "$owner"; then
2441 crview4article $rowid
2442 n=`query "SELECT count(*) FROM writeusers;"`
2443 ## err N=$n
2444 if [ $((n)) -gt 0 ]; then
2445 ## err ROWID=$rowid
2446 sql="SELECT growid || ':' || gname FROM movablegroups;"
2447 cat<<-EOF
2448 <div class="fold">
2449 `cgi_checkbox mv send id="mv"`<label
2450 for="mv">この話題をグループ所有に移動する</label>
2451 <div>
2452 <form action="?mvart" method="POST" enctype="multipart/form-data">
2453 移動先グループ:
2454 <select name="mv2grp">
2455 EOF
2456 query ".mode html"
2457 query<<-EOF |
2458 $sql
2459 .mode list
2460 EOF
2461 sed -e '/<\/TR>/d' -e 's,<TR>,,' -e 's,TD>,option>,g' \
2462 -e 's,n>\([0-9]*\):\(.*\)<,n value="\1">\2<,'
2463 cat<<-EOF
2464 </select>
2465 <p>(移動できるグループは、この「話題」に書き込んでいる人全てが
2466 そのグループに加入しているものに限られます)</p>
2467 <p>`cgi_checkbox cfm yes`<label>確認
2468 (この操作は元に戻すことができません)</label></p>
2469 `cgi_hidden blogrowid $rowid`
2470 `cgi_submit 移動`
2471 `cgi_reset Reset`
2472 </form>
2473 </div>
2474 </div>
2475 EOF
2476 fi
2477 # end of isuser "$owner"
2478 elif { hexteams=$(hexteams "$owner" ) # blog is of GROUP
2479 [ -n "$hexteams" ];}; then
2480 none="`echo なし|hexize`"
2481 cat<<-EOF
2482 <div class="fold">
2483 `cgi_checkbox mv2team send id="mv2team"`<label
2484 for="mv2team">この話題を以下のチームのものにする</label>
2485 <div><p>現在の所属チーム設定:
2486 `query "SELECT
2487 coalesce((SELECT val FROM blog_s
2488 WHERE id=(SELECT id FROM blog WHERE rowid=$rowid)
2489 AND key='team'),
2490 ':なし');"`</p>
2491 <form action="?mvart" method="POST" enctype="multipart/form-data">
2492 移動先チーム: `cgi_select_h mv2team $none $hexteams`
2493 <p>`cgi_checkbox cfm yes`<label>確認</label></p>
2494 `cgi_hidden blogrowid $rowid`<br>
2495 `cgi_submit 移動`
2496 `cgi_reset Reset`
2497 </form></div></div>
2498 EOF
2499 fi
2501 mvart() { # move diary to some group or team
2502 # or move blog of group to team which belong to the group
2503 blogrowid=`getpar blogrowid`
2504 cfm=`getpar cfm`
2505 ##### echo move blog:$blogrowid to $mv2grp | html p
2506 blogrowid=${blogrowid%%[!A-Z0-9a-z_]*} # Purify
2507 . ./s4-blog.sh
2508 if [ -z "$blogrowid" ]; then
2509 echo "無効な指定です(mvart)。" | html p
2510 return
2511 elif [ x"$cfm" != x"yes" ]; then
2512 echo "記事移動の確認にチェックがないので通常表示に戻ります。" | html p
2513 elif { mv2grp=`getpar mv2grp`
2514 mv2grp=${mv2grp%%[!A-Z0-9a-z_]*} # Purify
2515 [ -n "$mv2grp" ]; }; then
2516 crview4article $blogrowid
2517 ########## TRANSACTION BEGIN
2518 query "BEGIN;"
2519 n=`query "SELECT count(*) FROM writeusers;"`
2520 ## err Nwriteuser=$n
2521 if [ $((n)) -gt 0 ]; then
2522 query<<-EOF
2523 UPDATE blog_s SET val=(SELECT gname FROM grp WHERE rowid=$mv2grp)
2524 WHERE key='owner'
2525 AND id=(SELECT id FROM blog WHERE rowid=$blogrowid)
2526 AND $mv2grp IN (SELECT growid FROM movablegroups);
2527 EOF
2528 fi
2529 query "END;"
2530 ########## TRANSACTION END
2531 elif { mv2team=`getpar mv2team|sed "s/'/''/g"`
2532 [ -n "$mv2team" ];}; then
2533 # blog owner can move it to ANY team
2534 case "$mv2team" in
2535 'なし')
2536 cat<<-EOF
2537 DELETE FROM blog_s
2538 WHERE id=(SELECT id FROM blog WHERE rowid=$blogrowid)
2539 AND key='team';
2540 EOF
2541 ;;
2542 "") ;;
2543 *)cat<<-EOF
2544 BEGIN;
2545 REPLACE INTO blog_s(id, key, val)
2546 VALUES((SELECT id FROM blog WHERE rowid=$blogrowid),
2547 'team', '$mv2team');
2548 REPLACE INTO blog_s(id, key, val)
2549 VALUES((SELECT id FROM blog WHERE rowid=$blogrowid),
2550 'notify', 'all'); -- Change notify to all
2551 END;
2552 EOF
2553 esac | query
2554 fi
2555 blog_reply $blogrowid
2556 echo yes | html p
2558 editart() { # $1=article-rowid $2=blogrowid
2559 rowid=${1%%[!A-Z0-9a-z_]*}
2560 blogrowid=${2%%[!A-Z0-9a-z_]*}
2561 if [ -z "$rowid" -o -z "$blogrowid" ]; then
2562 echo "表示する記事番号が未指定です。" | html p
2563 return
2564 fi
2565 owner=`getvalbyid blog owner $blogrowid`
2566 title=`getvalbyid blog title $blogrowid`
2567 author=`getvalbyid article author $rowid`
2568 ## err EDITart: owner=$owner, author=$author
2569 if isgrpowner $user $owner; then
2570 : EDIT OK
2571 elif [ x"$owner" != x"$user" -a x"$author" != x"$user" ]; then
2572 echo "本人か所有者しか編集できません." | html p
2573 return
2574 fi
2575 aid=`query "select id from article where rowid=$rowid;"`
2576 tmpout=$tmpd/editart.$$.out
2577 GF_ACTION="?replyblog+$blogrowid#$aid" \
2578 edittable $formdir/article.def article $rowid \
2579 > $tmpout
2580 rm -f /tmp/editart.out
2581 # Cannot use pipelining to m4 with genform() because of stdin stack
2582 _m4 -D_TITLE_="コメントの修正" -D_DIARY_="" \
2583 -D_FORM_="syscmd(cat $tmpout)" \
2584 -D_SUBTITLE_="`gecos $owner`の「$title」" \
2585 -D_BLOGS_= -D_DUMPTABLE_= \
2586 $layout/html.m4.html $layout/form+dump.m4.html
2588 send2mem() {
2589 rowid=`getpar grp`
2590 if [ -z "$rowid" ]; then
2591 echo "グループが未指定です。" | html p
2592 return
2593 fi
2594 message=`getpar message`
2595 if [ -z "$message" ]; then
2596 echo "文章を入れてください。" | html p
2597 return
2598 fi
2599 grp=`getgroupbyid $rowid`
2600 members=`collectemail $grp`
2601 # smail rcpt subj (file)
2602 smail "$members" "グループ $grp 宛メッセージ(from `gecos $user`)" <<EOF
2603 $urlbase?grp+$rowid
2604 グループ $grp に所属する
2605 `gecos $user` さんよりメッセージ:
2607 $message
2608 EOF
2609 cat<<EOF
2610 <p>以下の宛先に送信しました。</p>
2611 <pre>
2612 $members
2613 </pre>
2614 <p><a href="?grp+$rowid">グループ $grp</a>に戻る。</p>
2615 EOF
2617 joingrpadmit() {
2618 # $1=yes/no $2=session-key
2619 if [ -z "$2" ]; then
2620 echo "bye bye" | html p; return
2621 fi
2622 t_usr=`session=$2 getpar user`
2623 t_grp=`session=$2 getpar group`
2624 ## err joingrpadmit: t_usr=$t_usr, t_grp=$t_grp
2625 _m4 -D_TITLE_="joingrp" $layout/html.m4.html
2626 if [ -z "$t_usr" -o -z "$t_grp" ]; then
2627 echo "無効な加入依頼です。" | html p
2628 echo "有効期限が切れたか、
2629 他の管理者がいる場合は処理済みの可能性があります。" | html p
2630 return
2631 fi
2632 if ! isgrpowner "$user" $t_grp; then
2633 echo "グループ管理者のみの機能です。" | html p; return
2634 fi
2635 case $1 in
2636 yes) joingrp "$t_grp" "$t_usr" yes ;;
2637 no) joingrp "$t_grp" "$t_usr" no ;;
2638 *)
2639 echo "無効な指定です($1)。" | html p
2640 return ;;
2641 esac
2642 gid=$(query "select rowid from grp where gname=`sqlquote $t_grp`;")
2643 rcpts="`getgroupadminmails $t_grp` $user"
2644 ## err admit: msgdir=$msgdir, rcpts="["$rcpts"]"
2645 body="グループ <a href=\"?grp+$gid\">$t_grp</a>
2646
2647 $t_usr
2648 `[ x$1 = xyes ] && echo 'を追加' || echo 'の解除操作を'`
2649 しました。"
2650 (echo "$body"; echo; echo "$url?grp+$gid") | smail "$rcpts" "joingrp $1"
2651 query "delete from session where id='$2';"
2652 echo "$body" | html p
2655 joingrprequest() {
2656 # $1=group $2=user $3=yes/no $4=email(if any $5=AsAdmin)
2657 jss="joingrp-`date +%s`-`genrandom 12`"
2658 addsession $jss +${memoplimitdays}days
2659 query "replace into par values('$jss', 'group', 'string', `sqlquote $1`),
2660 ('$jss', 'user', 'string', `sqlquote $user`);"
2661 smail "$(collectemail `getgroupadmins $1`)" "Join request to $1"<<EOF
2662 $url
2663 $user さんから
2664 グループ $1
2665 に加入依頼がありました。
2667 承認する:
2668 $urlbase?joingrpadmit+yes+$jss
2670 白紙に戻す:
2671 $urlbase?joingrpadmit+no+$jss
2672 EOF
2673 echo "管理者に加入依頼を出しました。
2674 ${memoplimitdays}日以内に加入承認操作がされれば加入できますが、
2675 グループ運用方針に懸かることですので直接の問い合わせが重要です。" | html p
2677 joingrp() {
2678 # $1=group $2=user $3=yes/no $4=email(if any $5=AsAdmin)
2679 ## err joingrp: \$1=$1 \$2=$2 \$3=$3 \$4=$4
2680 if isgrpowner "$user" "$1"; then
2681 isowner="yes"
2682 elif [ -n "$5" ]; then
2683 isowner="yes"
2684 else
2685 isowner=""
2686 fi
2687 ## err jg:isgrpowner: isowner="$isowner"
2688 if [ -n "$isowner" ]; then
2689 : # GROUP OWNER CAN DO EVERYTHING ABOUT REGISTRATION/RETIREMENT
2690 elif [ x"$2" != x"$user" ]; then # if user is not login user
2691 echo "本人か、グループ管理者しか加入操作はできません。" | html p
2692 return
2693 elif [ x"$3" = x"no" ]; then
2694 : # Do not pursue those who leave
2695 elif [ x"$3" = x"yes" ] && ismember "$user" "$grp"; then
2696 : # Member can change own email address for the joining moderated group
2697 else # adding user is $user itself
2698 case `getgroupattr $1 regmode` in
2699 moderated)
2700 joingrprequest "$@" # Request only
2701 return
2702 ;;
2703 *)
2704 ;;
2705 esac
2706 fi
2707 qgname=`sqlquote $1`
2708 cond="where gname=$qgname and user='$2'"
2709 if [ x"$3" = x"yes" ]; then
2710 query "replace into grp_mem values($qgname, '$2');"
2711 if [ -n "$4" ]; then
2712 if msg=`emaildomaincheck "$4"`; then
2713 query "replace into grp_mem_s values($qgname, '$user', 'email', \
2714 'string', '$4', NULL);"
2715 else
2716 echo $msg
2717 fi
2718 else
2719 query "delete from grp_mem_s $cond and key='email';"
2720 fi
2721 if [ -n "$5" ]; then # as ADMIN
2722 # Coming here means newly created group
2723 sql="select case\
2724 when (select count(*) from grp_mem where gname=$qgname)=1\
2725 then (select user from grp_mem\
2726 where gname=$qgname and user='$user')\
2727 else '' end; "
2728 err NewGrpChk: $sql
2729 if [ -n "`query \"$sql\"`" ]; then
2730 ## err ADMIN: "replace into grp_adm values($qgname, '$user');"
2731 query "replace into grp_adm values($qgname, '$user');"
2732 fi
2733 fi
2734 else
2735 query "delete from grp_mem $cond;
2736 delete from grp_mem_s $cond;
2737 delete from grp_mem_m $cond;"
2738 fi
2740 grp_add_team() (
2741 # $1=grp-rowid $2=team $3...=user-rowid(s)
2742 grp=`getgroupbyid $1`
2743 team=$2; shift; shift
2744 [ -z "$grid" -o -z "$team" -o -z "$1" ] && return
2745 { echo "BEGIN;"
2746 for user; do
2747 echo "REPLACE INTO grp_mem_m(gname, user, key, type, val) VALUES(\
2748 '$grp',\
2749 (SELECT name FROM user WHERE rowid=$user),\
2750 'team', 'string', '$team');"
2751 done
2752 echo "END;"
2753 } | query
2755 grp_rm_team() (
2756 # $1=grp-rowid $2=team $3...=user-rowid(s)
2757 grid=$1
2758 qgrp=$(sqlquote "`getgroupbyid $grid`")
2759 team=$2; shift; shift
2760 [ -z "$grid" -o -z "$team" ] && return
2761 { echo "BEGIN;"
2762 for user; do
2763 echo "DELETE FROM grp_mem_m\
2764 WHERE gname=$qgrp \
2765 AND user=(SELECT name FROM user WHERE rowid=$user)\
2766 AND key='team' AND val='$team';"
2767 done
2768 cat<<-EOF
2769 DELETE FROM blog_s
2770 WHERE rowid=(
2771 SELECT rowid
2772 FROM blog_s a
2773 WHERE key='team'
2774 AND id IN (SELECT id FROM blog_s WHERE key='owner' AND val=$qgrp)
2775 AND NOT EXISTS (SELECT * FROM grp_mem_m
2776 WHERE key='team' AND val=a.val -- a.val=team
2777 AND gname = (SELECT val FROM blog_s b
2778 WHERE a.id=b.id AND key='owner')
2779 ));
2780 EOF
2782 echo "END;"
2783 } | query
2785 grp_reg_adm() {
2786 # $1=grp-rowid $2...=user-rowid
2787 grid=$1
2788 grp=`getgroupbyid "$1"`
2789 if [ -z "$grp" ]; then
2790 echo "無効なグループIDです" | html p; return
2791 fi
2792 if ! isgrpowner $user "$grp"; then
2793 echo "$grp グループの管理者しかこの操作はできません。" | html p; return
2794 fi
2795 shift
2796 for urid; do
2797 newadm=`query "select name from user where rowid=$urid;"`
2798 if [ -z "$newadm" ]; then
2799 echo "指定ユーザIDがおかしいようです。" | html p; return
2800 fi
2801 err GRP_reg_adm: "replace into grp_adm values(`sqlquote $grp`, '$newadm');"
2802 err ismember $newadm $grp
2803 if ismember $newadm $grp; then
2804 # OK, go ahead
2805 getgname="(select gname from grp where rowid=$grid)"
2806 query "replace into grp_adm values($getgname, '$newadm');"
2807 # confirm insertion
2808 sql="select * from grp_adm where gname=$getgname and user='$newadm'"
2809 if [ -n "`query \"$sql;\"`" ]; then
2810 echo "追加完了: $newadm" | html p
2811 else
2812 echo "追加失敗($1 $urid)" | html p
2813 fi
2814 fi
2815 showgroup $grid
2816 done
2818 dumptable() {
2819 # $1=mode $2=Table $3=column-list-of-*_s(defaults to *) $4=conditions(if any)
2820 # textのフィールドだけ全てダンプにしたほうがいいか
2821 # $DT_VIEW sets link
2822 # 6/17の次: editリンクじゃなくてスレッドVIEWリンクでいいんちゃう?
2823 ### elink="<a href=\"$myname?edittable+$2+\\2\">EDIT</a>"
2824 VIEW=${DT_VIEW-replyblog}
2825 if [ -n "$VIEW" ]; then
2826 dvlink=" <a href=\"$myname?$VIEW+\\2\\3\">VI</a><a href=\"$myname?$VIEW+\\2#bottom\">EW</a>"
2827 fi
2828 # $DT_CHLD=ChildTable:BindColumn
2829 if [ -n "$DT_CHLD" ]; then
2830 _t=${DT_CHLD%:*} _i=${DT_CHLD#*:}
2831 cntall="(select count($_i) from $_t where $_i=a.id)"
2832 # XXX: Dirty workaround for slow subquery of acclog
2833 presql="CREATE TEMPORARY TABLE IF NOT EXISTS myacclog AS
2834 SELECT * FROM acclog WHERE user='$user' and tbl='$2';"
2835 cntnew="(select count(val) from ${_t}_s where key='ctime' \
2836 and id in (select id from $_t where $_i=a.id) \
2837 and val > coalesce((select time from myacclog where \
2838 tblrowid=a.rowid),\
2839 '1970-01-01'))"
2840 cnt="$cntnew as '新着', $cntall as '総数',"
2841 dt_class=" td2r td3r dumpblogs"
2842 fi
2843 # Construct join expression
2844 eav="" scols=""
2845 pk=`gettblpkey $2`
2846 substr=${dumpcollen:+"substr(val, 0, $dumpcollen)"}
2847 substr=${substr:-val}
2848 for col in ${3:-`gettbl_s_cols $2`}; do
2849 case $col in
2850 gecos) scols="$scols${scols:+, }${col#}"
2851 continue ;; # built-in column name
2852 *:*) col=${col%:*} as=${col#*:} ;;
2853 *) as=${col} ;;
2854 esac
2855 eav=$eav${eav:+,}" max(case key when '$col' then $substr end) as $as"
2856 scols="$scols${scols:+, }b.$as"
2857 done
2858 #case author when '$user' then a.rowid else '---' end as ID,
2859 sql=${DT_SQL:-"select \
2860 a.rowid as LINK,\
2861 $cnt\
2862 $scols from $2 a left join\
2863 (select $pk,$eav,
2864 max(case key when 'owner'
2865 then (SELECT gecos FROM gecoses WHERE name=val) END) as gecos
2866 from ${2}_s c group by $pk) b on a.$pk=b.$pk $4;"}
2867 ## err dt:SQL="`echo \"$presql$sql\"|tr -d '\n'`"
2868 cat<<EOF | sed "s,\(<TR><TD>\)\([1-9][0-9]*\)\(#[0-9a-fxs]*\)*</TD>,\1$elink$dvlink</TD>,"
2869 <div> <!-- for folding by check button (s4-funcs.sh:dumptable()) -->
2870 <div class="dumptable">
2871 <table class="b$dt_class">
2872 `sq -header -cmd ".mode $1" $db "$presql$sql"`
2873 </table>
2874 </div> <!-- dumptable -->
2875 </div> <!-- for folding by check button (s4-funcs.sh:dumptable()) -->
2876 EOF
2879 par2table() (
2880 # copy current parameters of par into destination table
2881 # $1=definition-file
2882 # Using $user and $session
2883 # Return value:
2884 # 0: Stored successfully
2885 # 1: Insufficient fillings
2886 # 2: No permission to modify the record
2887 # 3: Invalid rowid
2888 # 4: SUCCESS to delete
2889 # 5: Stop deletion for lack of confirm check
2890 # 6: Password length too short
2891 # 7: Password mismatch
2892 # 8: Old password incorrect
2893 rowid=`getpar rowid`
2894 if [ ! -e $1 ]; then
2895 echo "テーブル定義ファイルが見付かりません" | html p
2896 exit 1
2897 fi
2898 tbl=${1%.def}
2899 tbl=${tbl##*/}
2900 if [ -n "$rowid" ]; then # Modify existing entry
2901 if [ x"$tbl" = x"user" ]; then
2902 rowowner=`query "select name from $tbl where rowid=$rowid;"`
2903 elif [ x"$tbl" = x"grp" ]; then
2904 sql="select gname from $tbl where rowid=$rowid;"
2905 ##err p2t:grp:q $sql
2906 isgrpowner $user "`query $sql`" && rowowner=$user
2907 else
2908 rowowner=`query "select owner from $tbl where rowid=$rowid;"`
2909 rowowner=${rowowner:-`query "select author from $tbl
2910 where rowid=$rowid;"`}
2911 fi
2912 ### err rowowner=$rowowner
2913 if [ x"$user" != x"$rowowner" ]; then
2914 echo "他人のレコードはいじれないの" | html p
2915 return 2
2916 elif [ -z "$rowowner" ]; then
2917 echo "指定したレコードはないみたい" | html p
2918 return 3
2919 fi
2920 rm=`getpar rm` cfm=`getpar confirm`
2921 # Editing existent entry
2922 if [ x"$rm" = x"yes" ]; then
2923 if [ x"$rm$cfm" = x"yesyes" ]; then
2924 query "delete from $tbl where rowid=$rowid;"
2925 return 4
2926 else
2927 echo "消去確認のチェックがないので消さなかったの..." | html p
2928 return 5
2929 fi
2930 fi
2931 fi
2933 ts=${tbl}_s tm=${tbl}_m val="" pval="" formaster=""
2934 if [ -n "$rowid" ]; then
2935 # Update of existing record
2936 for col in `gettblcols $tbl`; do
2937 val=`getparquote $col`
2938 [ -z "$val" ] && continue
2939 ## err query "update $tbl set $col=$val where rowid=$rowid"
2940 ## XX: THIS IS DIRTY hack to ensure non-foreign key in blog_s
2941 sql="update $tbl set $col=$val where rowid=$rowid;"
2942 if [ x"$tbl" = x"grp" -a x"$col" = x"gname" \
2943 -o x"tbl" = x"user" -a x"$col" = x"name" ]; then
2944 ## User name cannot be changed with interface provided with this
2945 ## script. But we offer the trigger to change owner user
2946 ## of blog_s table.
2947 #err "select quote($col) from $tbl where rowid=$rowid;"
2948 old=`query "select quote($col) from $tbl where rowid=$rowid;"`
2949 cat<<-EOF | query
2950 BEGIN;
2951 $sql
2952 update blog_s set val=$val
2953 where key='owner' and val=$old;
2954 COMMIT;
2955 EOF
2956 ## XX: DIRTY Hack Ends here
2957 ## We should keep blog's owner as a single column which has
2958 ## foreign key constraint with primary key of grp/user.
2959 else
2960 query "$sql"
2961 fi
2962 done
2963 # Then, set up $pval for further insertion of tbl_s and tbl_m
2964 for col in `gettblpkey $tbl`; do
2965 val=`query "select $col from $tbl where rowid=$rowid;"|sed -e 's/\"/\"\"/g'`
2966 pval="$pval${pval:+, }\"$val\""
2967 done
2968 else
2969 # New entry
2970 # Generate values() for primary keys
2971 for col in `gettblpkey $tbl`; do
2972 # Genuine primary keys for _m and _s
2973 val=`getvalquote $tbl $col`
2974 [ -z "$val" ] && continue
2975 pval="$pval${pval:+, }$val"
2976 done
2977 ##err pval=$pval
2978 for col in `gettblfkey $tbl`; do
2979 # args for values() to insertion into master table
2980 val=`getvalquote $tbl $col`
2981 [ -z "$val" ] && continue
2982 formaster=$formaster"${formaster:+, }$val"
2983 done
2984 formaster="$pval${formaster:+, }$formaster"
2985 ## err formaster=$formaster
2986 if [ -z "$formaster" ]; then
2987 echo "項目を全て埋めてください" | html pre
2988 return 1
2989 fi
2990 ## err "replace into $tbl values($formaster);"
2991 query "replace into $tbl values($formaster);"
2992 ## Insertion to master table, done
2993 fi
2995 for kt in s m; do
2996 tb2=${tbl}_$kt
2997 for col in `gettbl_${kt}_cols $tbl`; do
2998 ptype=`getpartype $col "limit 1"`
3000 # First, check update of existing entries in _m
3001 if [ $kt = m ]; then
3002 # sessID|address.1.22|string|Somewhere-x.y.z
3003 sql=""
3004 ##err dots from query "select var from par where var like '$col.%';"
3005 for v in `query "select var from par where var like '$col.%';"`; do
3006 # v=address.1.22
3007 st_rowid=${v##*.}
3008 origcol=${v%%.*} # original column derived from
3009 ##err Updating for $v st_rowid=$st_rowid, partype=`getpartype $v`
3010 ##case `getpartype $v` in
3011 ## err CASE `gettbl_coltype $tbl/$origcol` in
3012 ## err edit flag = `getpar action.$v`
3013 case `getpar action.$v` in
3014 rm)
3015 if [ x`getpar confirm.$v` = x"yes" ]; then
3016 newsql="delete from $tb2"
3017 else
3018 echo "削除確認未チェック" | html p
3019 fi ;;
3020 edit)
3021 case `gettbl_coltype $tbl/$origcol` in
3022 image|document|binary)
3023 file=$tmpd/`getparfilename $v`
3024 ## err type=file=$file
3025 [ -z "$file" ] && continue
3026 bn=`sqlquotestr "${file##*/}"`
3027 bin="X'"$(hexize "$file")"'"
3028 ct=`file --mime-type - < "$file" |cut -d' ' -f2`
3029 type=\"file:$ct\"
3030 newsql="update $tb2 set val=$bn, type=$type, bin=$bin"
3031 cachedir=`getcachedir "$tbl/$rowid"`
3032 err getcache tbl/rowid=$tbl/$rowid, rm -r $cachedir
3033 rm -rf $cachedir
3034 ;;
3035 *)
3036 newsql="update $tb2 set val=(select val from par where var \
3037 like '$col.%.$st_rowid')"
3038 ;;
3039 esac
3040 ;;
3041 *) # maybe "keep", do not modify value
3042 continue
3043 ;;
3044 esac
3045 # err newsql=$newsql
3046 sql=$sql$nl"$newsql where rowid=$st_rowid;"
3047 done
3049 if [ x"$bin" = x"NULL" ]; then
3050 ## err repl:normal sql=`echo $sql`
3051 query "$sql
3052 delete from $tb2 where type='string' and val='';"
3053 ## err repl:normal done
3054 else
3055 sqlfile="$tmpd/sqlf.$$"
3056 echo "$sql" > $sqlfile
3057 ## err repl:sqlfile=`ls -lF $sqlfile`
3058 query ".read $sqlfile"
3059 ## err repl:done
3060 fi
3061 # Rest of kt==m: set multiple mode
3062 nr=`getparcount $col`
3063 else
3064 nr=1 # for kt==s, number of records is 1
3065 fi
3067 i=0
3068 while [ $i -lt $nr ]; do
3069 limit="limit 1 offset $i"
3070 i=$((i+1)) # increase beforehand against continue
3071 val=`getvalquote $tbl $col "$limit"`
3072 [ -z "$val" -o x"$val" = x'""' -o x"$val" = x"NULL" ] && continue
3073 ## err $col=$val
3074 bin=NULL
3075 ## err partype$col=`getpartype $col "$limit"`
3076 case $ptype in
3077 file) file=$tmpd/`getparfilename $col "$limit"`
3078 ## err parfile-$col=$file
3079 [ -z "$file" ] && continue
3080 bin="X'"$(hexize "$file")"'"
3081 ct=`file --mime-type - < "$file"|cut -d' ' -f2`
3082 type=\"file:$ct\" ;;
3083 "*"*) continue ;; # foreign table
3084 *) type=\"string\" ;;
3085 esac
3086 case `gettbl_coltype $tbl/$col` in
3087 password) # special care for password
3088 # name={password,pswd1,pswd2}
3089 p1=`getpar pswd1 "$limit"`
3090 if [ -z "$p1" ]; then
3091 continue # SKIP password setting, if p1 is empty
3092 else
3093 pswd=`getpar pswd "$limit"` p2=`getpar pswd2 "$limit"`
3094 ## err pswd=$pswd
3095 if pwcheck "$pswd"; then
3096 if [ x"$p1" = x"$p2" ]; then
3097 case "$p1" in
3098 ??????????*) ;;
3099 *) echo "パスワードは10字以上にしてください。" | html p
3100 return 6;;
3101 esac
3102 val="\"`echo $p1|mypwhash`\""
3103 else
3104 echo "2つの新パスワード不一致" | html p
3105 return 7
3106 fi
3107 else
3108 echo "旧パスワード違います" | html p
3109 return 8
3110 fi
3111 fi
3112 ;;
3113 esac
3114 ## err p2t: "replace into $tb2 values($pval, \"$col\", $type, $val, bin...);"
3115 #query "replace into $tb2 values($pval, \"$col\", $type, $val, $bin);"
3116 sql="replace into $tb2 values($pval, \"$col\", $type, $val, $bin);"
3117 if [ x"$bin" = x"NULL" ]; then
3118 ## err Normal-query: `echo $sql`
3119 query "$sql"
3120 else
3121 sqlfile="$tmpd/query.$$"
3122 echo "$sql" > $sqlfile
3123 ## err sqlfile=`ls -lF $sqlfile`
3124 query ".read $sqlfile"
3125 fi
3126 ## err p2t done
3127 done
3128 done
3129 done
3130 return 0
3131 ##err donee
3133 genform() {
3134 # $1 = form definition file
3135 # $2, $3 (optional)= table name and ROWID
3136 # If $GF_VIEWONLY set and nonNull, output values without form
3137 # If $GF_ARGS set, use it as content-strings in the form
3138 # If $GF_OWNER set, use it as value of name="owner"
3139 # If $GF_STAGE set, use it as value of name="stage"
3140 forms="" hiddens="" rowid=$3
3141 if [ ! -e "$1" ]; then
3142 echo "そのようなデータベースはないようです($2)。" | html p
3143 return
3144 elif [ -n "$2" ]; then
3145 rec=`query "select * from $2 where rowid='$rowid';"`
3146 if [ -z "$rec" ]; then
3147 pk=`gettblpkey $2`
3148 ###rec=`sq $db "select rowid from $2 where $pk='$rowid'"`
3149 rec=`query "select rowid from $2 where $pk='$rowid';"`
3150 rowid=$rec
3151 rec=$3
3152 fi
3153 if [ -z "$rec" ]; then
3154 echo "そんなレコードはないみたいね..." | html p
3155 return
3156 fi
3157 fi
3158 if [ -z "$GF_VIEWONLY" ]; then
3159 rm='<input id="rm" name="rm" type="checkbox"
3160 value="yes"><label for="rm">このエントリの削除</label>
3161 <span>ほんとうに消しますよ(確認)!
3162 <input name="confirm" type=checkbox value="yes">はい</span>'
3163 fi
3164 # Image Cache dir
3165 ## err genform: getcache=$2/$rowid
3166 td=`getcachedir "$2/$rowid"`
3167 while IFS=: read prompt name keytype type args; do
3168 [ -z "${prompt%%\#*}" ] && continue # skip comment line(#)
3169 sp="${args:+ }"
3170 form="" val=""
3171 if [ -n "$rowid" ]; then
3172 # err genform2a: Seeking for "$2.$name, type=$type"
3173 rawval=`getvalbyid $2 $name $rowid $td`
3174 val=`echo "$rawval"|htmlescape`
3175 ## err genform3a: getvalbyid $2 $name $rowid $td
3176 ## err genform3b: val="[$val]" type="$type"
3177 fi
3178 if [ -n "$GF_VIEWONLY" ]; then
3179 is_hidden "$2" "$name" && continue
3180 fi
3181 case "$type" in
3182 text*)
3183 cgiform=cgi_multi_$type
3184 if [ -s $td/$name.count -a -n "$val" ]; then
3185 form=`$cgiform $name $td`
3186 val=$(echo "$val"|
3187 while read fn; do
3188 echo "<tr><td>`cat $td/$fn|htmlescape|hreflink`
3189 </td></tr>$nl"
3190 done)
3191 val="<table>$nl$val$nl</table>"
3192 else
3193 #form="<input name=\"$name\" value=\"$val\" type=\"$type\"$sp$args>$nl"
3194 form=`cgi_$type $name "$rawval" "$args"`
3195 fi
3196 ;;
3197 [Rr][Aa][Dd][Ii][Oo])
3198 fh="<label><input type=\"radio\" name=\"$name\""
3199 form="`echo $args|sed -e \
3200 \"s,\([^ =][^=]*\)=\([^= ][^= ]*\),$fh value=\\"\2\\">\1</label>,g\"`"
3201 ;;
3202 [Cc][Hh][Ee][Cc][Kk][Bb][Oo][Xx])
3203 form="<label><input type=\"checkbox\" name=\"$name\" value=\"${args#*=}\">${args%=*}</label>"
3204 ;;
3205 [Ss][Ee][Ll][Ee][Cc][Tt])
3206 fh="<select name=\"$name\">$nl"
3207 form=$(for l in $args; do
3208 echo "<option value=\"${l#*=}\">${l%=*}</option>"
3209 done)
3210 if [ -n "$val" ]; then
3211 form=`echo $form|sed -e "s,\(value=.$val.\),\\1 selected,"`
3212 fi
3213 form="$fh$form</select>"
3214 ;;
3215 [Ii][Mm][Aa][Gg][Ee]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Bb]inary)
3216 if [ -s $td/$name.count ]; then
3217 form=`cgi_multi_file $name $td "$args"`
3218 if [ -n "$val" ]; then
3219 hrfb="$myname?showattc+$2_m"
3220 val=$(echo "$rawval" \
3221 | while read fn; do
3222 data=`percenthex "$td/$fn"`
3223 #ct=`cat $td/$fn.content-type`
3224 ct=`file --mime-type - < "$td/$fn"|cut -d' ' -f2`
3225 ri=`cat "$td/$fn.rowid"`
3226 ## err fn=$fn, name=$name, ri=$ri; ls -lF "$td/" 1>&3
3227 #imgsrc="<img src=\"data:$ct,$data\">"
3228 #echo "<a href=\"$hrfb+$ri\">$imgsrc</a><br>"
3229 iconhref "$td/$fn" "$hrfb+$ri" ""
3230 done)
3231 fi
3232 else
3233 form="<input type=\"file\" name=\"$name\" $args>"
3234 if [ -n "$val" ]; then
3235 imgs=$(echo "$rawval"\
3236 |while read fn;do
3237 data=`percenthex "$td/$fn"`
3238 echo "<img src=\"data:image/png,$data\">$fn<br>"
3239 done)
3240 form=$form"<br>$imgs"
3241 val=$imgs # 2015-06-15
3242 else
3243 form="<input type=\"file\" name=\"$name\" $args>"
3244 fi
3245 fi
3246 ;;
3247 [Hh][Ii][Dd][Dd][Ee][Nn])
3248 if [ -n "$GF_STAGE" -a x"$name" = x"stage" ]; then
3249 args="value=\"$GF_STAGE\""
3250 fi
3251 form="<input type=\"hidden\" name=\"$name\" $args>"
3252 prompt='' # Remove prompt
3253 ;;
3254 [Aa][Uu][Tt][Hh][Oo][Rr])
3255 form="<input type=\"hidden\" name=\"author\" value=\"$user\">"
3256 prompt="" ;;
3257 [Oo][Ww][Nn][Ee][Rr])
3258 val=${GF_OWNER:-$val}
3259 val=${val:-$user}
3260 form="<input type=\"hidden\" name=\"owner\" value=\"$val\">"
3261 prompt="" ;;
3262 [Uu][Ss][Ee][Rr])
3263 # XXX: is null $user ok?
3264 #form="<input type=\"hidden\" name=\"user\" value=\"$user\">"
3265 [ -n "$GF_VIEWONLY" ] && continue
3266 form="$user"
3267 ;;
3268 [Pp]assword)
3269 [ -n "$GF_VIEWONLY" ] && continue
3270 form="`cgi_passwd`"
3271 val=""
3272 ;;
3273 [Ss][Ee][Rr][Ii][Aa][Ll]|[Ss][Tt][Aa][Mm][Pp])
3274 if [ -z "$rowid" ]; then
3275 val=`genserial`
3276 fi
3277 form="<input type=\"hidden\" name=\"$name\" value=\"$val\">"
3278 prompt="" ;;
3279 [Ss][Ee][Ss][Ss][Ii][Oo][Nn])
3280 prompt=""
3281 ;;
3282 parent|path|blog*)
3283 prompt=""
3284 ;;
3285 "*"*)
3286 tail=$tail"``"
3287 continue ;;
3288 esac
3289 if [ -n "$prompt" ]; then
3290 if [ -n "${GF_VIEWONLY}" ]; then
3291 form=$val
3292 else
3294 fi
3295 forms=$forms" <tr class=\"$name\"><th>$prompt</th><td>$form</td></tr>$nl"
3296 else
3297 hiddens=$hiddens$nl"$form"
3298 fi
3299 done < $1
3300 # enctype="multipart/form-data"
3301 cat<<EOF
3302 <form action="${GF_ACTION:-$myname}" method="POST" enctype="multipart/form-data">
3303 ${rowid:+$rm}
3304 <table class="b $2">
3305 $forms
3306 </table>$hiddens
3307 ${GF_STAGE:+`cgi_hidden stage $GF_STAGE`}
3308 ${rowid:+<input type="hidden" name="rowid" value="$rowid">}
3309 EOF
3310 if [ -z $GF_VIEWONLY ]; then
3311 cat<<EOF
3312 <input type="submit" name="sub" value="OK">
3313 <input type="reset" name="res" value="Reset">
3314 EOF
3315 fi
3316 cat<<EOF
3317 $GF_ARGS</form>
3318 $tail
3319 EOF
3321 edittable() {
3322 # $1=form-def $2=table $3 rowid
3323 genform "$@"
3325 viewtable() {
3326 GF_VIEWONLY=1 genform "$@"
3328 showattc() {
3329 # $1=table_m $2=rowid &optional $3=RawFlag
3330 ## err \$1=$1 \$2=$2 \$3=$3
3331 if ! isfilereadable $user $1 $2; then
3332 contenttype; echo
3333 echo "このファイルは管理者のみしか見られません" | html p
3334 putfooter; exit
3335 fi
3336 idir=`umask 002; mktempd` || exit 1
3337 # tmpfiles=$tmpfiles"${tmpfiles+ }$idir"
3338 bin=$idir/$myname-$$.bin
3339 sql="select quote(bin) from $1 where rowid='$2';"
3340 ## err showattc: sql: $sql
3341 sq $db "$sql" | unhexize > $bin
3342 tv=`query "select type||'//'||val from $1 where rowid='$2';"`
3343 type=${tv%//*} fn=${tv#*//}
3344 ## err tv=$tv type=$type fn=$fn, tp2=${tv%\|*}
3345 ct=${type#file:}
3346 case $ct in # all text/* changed to text/plain
3347 text/*)
3348 charset=`nkf -g $bin|cut -d' ' -f1`
3349 case $charset in
3350 ASCII*) charset="" ;;
3351 esac
3352 if [ -z "$3" ]; then
3353 ct="text/html${charset:+; charset=$charset}"
3354 link="?showattc+$1+$2+raw"
3355 cat $bin | htmlescape \
3356 | sed 's,^,<span></span>,' \
3357 | _m4 -D_TITLE_="$fn" -D_CONTENT_TYPE_="$ct" \
3358 -D_LINK_="$link" \
3359 -D_BODY_="syscmd(\`cat')" $layout/pretty.m4.txt
3360 exit $?
3361 fi
3362 ct="text/plain${charset:+; charset=$charset}"
3363 ;;
3364 esac
3365 contenttype "$ct"
3366 echo "Content-Disposition: filename=\"$fn\""
3367 echo "Content-Length: " `cat $bin | wc -c`; echo
3368 #echo "Content-Type: " ${type#file:}; echo
3369 cat $bin
3372 # Some default stupid handler on CGI values
3374 default_storedb() {
3375 # ARG: $1=table-def-file
3376 # RET: $tbl=table-name, $col=mail-column, $cols=columns
3377 tbl=`basename $1`
3378 tbl=${tbl%.def}
3379 cols="`grep :text $1|cut -d: -f2`"
3380 col=`echo "$cols"|head -1`
3381 vcol=`getpar $col`
3382 err default0: \$1=$1 col=$col cols="[$cols]" vcol=$vcol
3383 if [ -n "$vcol" ]; then
3384 par2table $1
3385 else
3386 return 2 # No insertion occurred
3387 fi
3390 default_view() { # $1=def-file
3391 ### DT_VIEW="edittable+$tbl" dumptable html $tbl "$cols" \
3392 ## DT_VIEW="edittable+$tbl" dumptable html $tbl "name memo file" \
3393 default_storedb "$@"
3394 query "select rowid from $tbl order by rowid desc;" \
3395 | while read rowid; do
3396 viewtable $1 $tbl $rowid
3397 done | _m4 -D_TITLE_="$tbl" \
3398 -D_FORM_="`genform $1`" \
3399 -D_DUMPTABLE_="syscmd(cat)" \
3400 $layout/html.m4.html $layout/form+dump.m4.html
3402 default_viewtext() { # $1=def-file
3403 ### DT_VIEW="edittable+$tbl" dumptable html $tbl "$cols" \
3404 default_storedb "$@"
3405 DT_VIEW="viewtable+$tbl" dumptable html $tbl "name memo file" \
3406 | _m4 -D_TITLE_="$tbl" \
3407 -D_FORM_="`genform $1`" \
3408 -D_DUMPTABLE_="syscmd(cat)" \
3409 $layout/html.m4.html $layout/form+dump.m4.html
3411 default_smail() {
3412 default_storedb "$@"
3413 if [ $? -eq 2 ]; then
3414 _m4 -D_TITLE_="入力" \
3415 -D_FORM_="`genform $1`" \
3416 -D_DUMPTABLE_="" \
3417 $layout/html.m4.html $layout/form+dump.m4.html
3418 return
3419 fi
3420 cond=""
3421 for pk in `gettblpkey $tbl`; do
3422 pv=$(sqlquote $(getpar $pk))
3423 cond="$cond${cond:+ and }$pk=$pv"
3424 done
3425 sql="select rowid from $tbl where $cond;"
3426 rowid=`query "$sql"`
3427 ## err smail1 - "$sql" "-> rowid=$rowid"
3429 while IFS=: read prompt name keytype type args; do # Read from $1
3430 val=`getpar $name`
3431 if [ -n "$val" ]; then
3432 text="$text
3433 $prompt
3434 $name=$val
3435 ---------------------------------------------------------"
3436 fi
3437 case "$type" in
3438 image|document|file)
3439 fn="`getvalbyid $tbl $name $rowid $tmpd`"
3440 fns=$(echo "$fn"|while read fn; do
3441 err mv $tmpd/$fn.orig $tmpd/$fn
3442 mv $tmpd/$fn.orig $tmpd/$fn
3443 rm $tmpd/$fn.rowid # Remove cache flag
3444 ## err "`ls $tmpd/$fn`"
3445 echo $fn
3446 done)
3447 files="$files $fns"
3448 ;;
3449 esac
3450 done < $1
3451 ## err FILES=$files "`ls -lF $tmpd`"
3452 subj="from ${REMOTE_ADDR}"
3453 (echo "$url"
3454 echo "への書き込みがありました。"
3455 echo "------"
3456 echo "$text"
3457 ) | (cd $tmpd &&
3458 err LS="`ls -lF`" &&
3459 $mydir/sendmultipart.sh -t "$admin" -s "$subj" $files)
3460 _m4 -D_TITLE_="入力完了" $layout/html.m4.html
3461 echo "以下の内容で送信しました。" | html p
3462 viewtable $1 $tbl \
3463 `query "select rowid from $tbl order by rowid desc limit 1;"`
3464 echo "戻る" | html a "href=\"?\""