s4

view s4-funcs.sh @ 378:165fa0a61d18

pre.list clear:both
author HIROSE Yuuji <yuuji@gentei.org>
date Sun, 27 Nov 2016 17:24:22 +0859
parents d09dd70730a0
children 7b1c44bf12e8
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 colmd=" mode"
2062 fi
2063 if [ -n "$ismember" ]; then
2064 echo "${iamowner:+ / }<a href=\"?blog+$rowid\">グループの新規話題作成</a>"
2065 echo "/ <a href=\"?grpaction+$rowid\">メンバーを個別選択しての操作</a></p>"
2066 # div.fold input[type="checkbox"]:checked ~ div {display: block;}
2067 cat<<EOF
2068 <form action="?send2mem" method="POST" enctype="multipart/form-data">
2069 <div class="fold clear">
2070 `cgi_checkbox send yes id="send"`<label
2071 for="send">グループ全員にメッセージ送信</label>
2072 <div>
2073 `cgi_textarea message "" "cols=60"`
2074 `cgi_submit 送信`
2075 `cgi_reset リセット`
2076 </div>
2077 `cgi_hidden grp $rowid`
2078 </div></form>
2079 EOF
2080 fi
2081 # 加入ボタン + 加入者リスト
2082 if [ -n "$ismember" ]; then
2083 ismem='checked' state="(参加中)"
2084 else
2085 nomem='checked' state="(現在非加入)"
2086 fi
2087 # このグループでの加入アドレス
2088 eml=`query "select val from grp_mem_s where gname=$qgrp and user='$user' \
2089 and key='email';"`
2090 ##err EML: "select val from grp_mem_s where gname='$2' and user='$user' \
2091 ## and key='email';"
2092 ##err email=$eml
2093 cat <<EOF
2094 <div class="fold clear">
2095 `cgi_checkbox reg yes id="reg"`<label
2096 for="reg">自身の加入状態を操作する</label>$state
2097 <div>
2098 EOF
2099 cgi_form grp <<EOF
2100 <p>このグループに</p>
2101 <table class="b">
2102 <tr><th>メンバーとして</th><td>
2103 <label>`cgi_radio joingrp "yes" $ismem`参加</label> /
2104 <label>`cgi_radio joingrp "no" $nomem`参加しない</label></td></tr>
2105 <tr><th>参加する場合のメイルアドレス<br>
2106 <small>(メインのアドレスとは違うものにする場合に記入<br>
2107 同じでよい場合は空欄に)</small></th>
2108 <td>`cgi_text email $eml`</td></tr>
2109 </table>
2110 `cgi_hidden grp $rowid`
2111 EOF
2112 if [ x`getgroupattr $grp regmode` = x'moderated' -a -z "$ismem" ]; then
2113 echo "moderated (承認加入の)グループなので実際に参加できるのは
2114 グループ管理者が承認操作をした後になります。" | html p 'class="warn"'
2115 fi
2116 echo '</div></div>'
2117 echo '<h2>話題一覧</h2>'
2118 cgi_form searchart<<EOF
2119 <label>`cgi_text kwd`という語を含むコメントを検索</label>
2120 `cgi_hidden owner $grp`
2121 EOF
2122 cond="where a.id in (select id from blog_s where key='owner' and val=$qgrp) order by ctime desc"
2123 DT_CHLD=article:blogid \
2124 DT_VIEW=replyblog dumptable html blog \
2125 "ctime team title heading$colmd" "$cond"
2127 getgname="(select gname from grp where rowid=$rowid)"
2128 c="group by a.name having a.name in (select user from grp_mem where gname=$getgname)"
2129 cm="?commission+$rowid"
2130 thumbxy=50x50 listmember "" "$c" "$rowid" \
2131 |sed -e "s|\(<br>\),not=\(.*\)|\1|" # 間違って押しやすい
2132 # team list
2133 hexteams=`hexteams "$grp"`
2134 if [ -n "$hexteams" ]; then
2135 echo "チーム一覧" | html h2
2136 echo '<div class="dumptable"><table class="b">'
2137 sq $db -html -header<<-EOF
2138 SELECT val TEAM,
2139 group_concat((SELECT gecos FROM gecoses WHERE name=user), ',')
2140 MEMBERS
2141 FROM grp_mem_m WHERE gname=$qgrp AND key='team' GROUP BY val;
2142 EOF
2143 echo '</table></div>'
2144 fi
2146 grp_getbodyclass() {
2147 # Get css class name for document.
2148 # `moderated' for moderated groups
2149 # `ismember' for groups where user belongs
2150 # $1=GroupName (w/o quote)
2151 # $user=userNameCurrentlyLogin
2152 ## err grp_getbodyclass: 1="$1"
2153 qgrp=`sqlquote "$1"`
2154 query<<-EOF
2155 SELECT coalesce(
2156 (SELECT val FROM grp_s WHERE gname=$qgrp AND key='regmode'),
2157 'open')
2158 ||
2159 CASE WHEN '$user'
2160 IN (SELECT user FROM grp_mem WHERE gname=$qgrp)
2161 THEN ' ismember'
2162 ELSE ''
2163 END;
2164 EOF
2166 grpaction() { # $1=group-rowid
2167 err GRP_ACTION:IN
2168 grid=${1:-`getpar grp`}
2169 grp=`getgroupbyid "$grid"`
2170 if [ -z "$grp" ]; then
2171 echo "無効な指定です。" | html p; return
2172 fi
2173 if ! ismember $user $grp; then
2174 echo "加入者のみに許可された操作です。" | html p; return
2175 fi
2176 echo "グループ $grp 個別選択操作" \
2177 | _m4 -D_TITLE_="syscmd(\`cat')" \
2178 -D_BODYCLASS_="`grp_getbodyclass \"$grp\"`" \
2179 $layout/html.m4.html
2181 isowner=""
2182 isgrpowner "$user" "$grp" && isowner="yes"
2183 usel=`getpar usel`
2184 if [ -n "$usel" ]; then
2185 uids=$(echo `echo $usel`|tr ' ' ',')
2186 ## err grpaction-1: grp=$grp, `echo $sql`
2187 text=`getpar text`
2189 rm=`getpar rm` cfm=`getpar confirm`
2190 ## err rm=$rm cfm=$cfm
2191 if [ x"$rm" = x"yes" ]; then
2192 if [ "$isowner" ]; then
2193 if [ x"$rm$cfm" = x"yesyes" ]; then
2194 # Eliminate
2195 cond="where gname=(select gname from grp where rowid=$grid) and user in (select name from user where rowid in ($uids))"
2196 for tbl in grp_mem grp_mem_s grp_mem_m; do
2197 sql="delete from $tbl $cond;"
2198 # echo "sql=$sql"
2199 query "$sql"
2200 err rmGRPuser "$sql"
2201 done
2202 num=`query "select count(*) from user where rowid in ($uids);"`
2203 #err num=$num
2204 if [ 0$num -gt 0 ]; then
2205 sql="select coalesce(b.val,a.name) from user a left join \
2206 user_s b on a.name=b.name and key='gecos' where a.rowid in ($uids);"
2207 # err `echo "$sql"`
2208 html pre<<EOF
2209 以下の${num}名のグループ $grp 登録を解除しました。
2210 `query "$sql"`
2211 EOF
2212 fi
2213 else
2214 echo "確認のチェックがないのでやめておきます。" | html p
2215 return
2216 fi
2217 else # not Group Owner
2218 echo "グループ管理者でないのでメンバー操作はできません。" | html p
2219 return
2220 fi
2221 cat<<EOF
2223 EOF
2224 elif [ x"$rm" = x"send" ]; then # if sendmsg mode
2225 if [ -z "$text" ]; then # if msg is empty
2226 echo "なにかメッセージを..." | html p
2227 return 0
2228 fi
2229 gecos=`gecos $user`
2230 mkfrom=`getpar mkfrom`
2231 if [ x"$mkfrom" = x"yes" ]; then
2232 safegc=`echo "$gecos" | tr -d '<>@'`
2233 myuid=`query "SELECT rowid FROM user WHERE name='$user';"`
2234 fromad=`email4groupbyuid "$grp" "$myuid"`
2235 mail_from="$safegc <$fromad>"
2236 else
2237 mail_from="$admin"
2238 fi
2239 MAIL_FROM=$mail_from \
2240 smail "`email4groupbyuid "$grp" $usel` $user" \
2241 "$gecos さんからのメッセージ" <<EOF
2242 $url
2243 のグループ「$grp」のメンバーである $gecos さんから、
2244 あなた宛へのメッセージです。
2245 ----------------------------------------------------------
2246 $text
2247 EOF
2248 if [ $? = 0 ]; then
2249 echo "Note: 以下のメンバーにメッセージを送信しました。" | html p
2250 sql="select coalesce(b.val, a.name) from
2251 (select name from user where rowid in ($uids)) a
2252 left join user_s b on a.name=b.name and b.key='gecos';"
2253 html pre<<EOF
2254 `query "$sql"`
2255 (送信者である $gecos さんも含まれます)
2256 EOF
2257 err SendDone: `echo $sql`
2258 fi
2259 elif [ x"$rm" = x"commission" ]; then
2260 grp_reg_adm $grid $usel
2261 elif [ x"$rm" = x"addteam" ]; then
2262 team=`getpar team|sed "s/'/''/g"` # for single quotation
2263 newteam=`echo "$team"|tr -d ,`
2264 if [ x"$team" != x"$newteam" ]; then
2265 echo "チーム名に使えない文字を除去しました" | html p
2266 team=newteam
2267 fi
2268 if [ -z "$team" -o x"$team" = x"なし" ]; then
2269 cat<<-EOF | html p
2270 有効なチーム名を入力してください。
2271 カンマだけ、「なし」という名前は使えません。
2272 EOF
2273 echo "有効なチーム名を入力してください。" | html p
2274 else
2275 grp_add_team $grid "$team" $usel
2276 fi
2277 elif [ x"$rm" = x"rmteam" ]; then
2278 if [ x"yes" = x"`getpar teamconfirm`" ]; then
2279 rmteam=`getpar rmteam|sed "s/'/''/g"`
2280 if [ -n "`query \"SELECT val FROM grp_mem_m WHERE\
2281 gname='$grp' AND user='$user' AND key='team'\
2282 AND val='$rmteam';\"`" ]; then
2283 grp_rm_team $grid "$rmteam" $usel
2284 else
2285 echo "所属していないチームの除去操作はできません。"|html p
2286 fi
2287 else
2288 echo "確認チェックなしなのでチーム除去しませんでした。"|html p
2289 fi
2290 fi
2291 fi
2292 # New entry
2293 sql="select /* Ahh, ugly SQL, I wanna fix... */
2294 case
2295 when (select user from grp_adm where
2296 gname=(select gname from grp where rowid=$grid)
2297 and user=a.name) is not null
2298 then 'k'
2299 else ''
2300 end || a.rowid||
2301 ','||a.gecos as NAME,
2302 (SELECT count(author) /* Put post count for scoring 2016-08-01 */
2303 FROM article NATURAL JOIN article_s
2304 WHERE blogid IN
2305 (SELECT id FROM blog_s
2306 WHERE key='owner'
2307 AND val=(SELECT gname FROM grp where rowid=$grid))
2308 AND author=a.name AND key='text')
2309 as POST,
2310 (SELECT group_concat(val, ',')
2311 FROM grp_mem_m
2312 WHERE gname='$grp' AND user=a.name AND key='team') as TEAM
2313 FROM
2314 gecoses a
2315 WHERE name in (select user from grp_mem where
2316 gname=(select gname from grp where rowid=$grid))
2317 ORDER by a.gecos;"
2318 ## err grpaction: "`echo \"$sql\"`"
2319 tf=$tmpd/title.$$
2320 echo "グループ[<a href=\"?grp+$grid\">$grp</a>]参加メンバーに対する操作" > $tf
2321 cmmsg="`cgi_radio rm commission id=\"cmadmin\"`<label
2322 for=\"cmadmin\">グループ管理者委任</label>
2323 <div><p>このグループでの全権を付与します。信頼できる人に託してください。
2324 </p></div>"
2325 excmsg="`cgi_radio rm yes id=\"conf\"`<label
2326 for=\"conf\">グループ登録解除</label>
2327 <div>本当に消します! `cgi_checkbox confirm yes` 確認
2328 <p>この操作による通知は本人に行きません。
2329 あらかじめ通知するか、登録解除してよい状況かしっかり確認してください。</p>
2330 </div>"
2331 # Get team list to which current user belongs into $hexteams
2332 myhexteams=$(hexteams "$grp" "$user")
2333 allhexteams=$(hexteams "$grp")
2334 if [ -n "$myhexteams" ]; then
2335 rmteammsg="`cgi_radio rm rmteam 'id=\"cmrmteam\"'`<label
2336 for=\"cmrmteam\">チーム属性除去</label>
2337 <div>チーム属性:`cgi_select_h rmteam \"2d2d2d\" $myhexteams`
2338 を除去します: `cgi_checkbox teamconfirm yes` 確認
2339 <p>この操作による通知は本人に行きません。
2340 あらかじめ通知するか、登録解除してよい状況かしっかり確認してください。</p>
2341 </div><!-- end of $rmteammsg -->
2343 fi
2344 b1='<label> <input type="checkbox" name="usel" value="'
2345 ba='<label class="admin"><input type="checkbox" name="usel" value="'
2346 #b2='"> <span>' b3='</span></label>'
2347 # | sed -e "s|^\(<TR><TD>\)k\([0-9]*\),\([^<]*\)|\1$ba\2$b2\3$b3|" \
2348 # -e "s|^\(<TR><TD>\)\([0-9]*\),\([^<]*\)|\1$b1\2$b2\3$b3|" \
2349 lnk='"> <span>\3</span></label> [<a href="?home+\2">HOME</a>]'
2350 cgi_form grpaction<<EOF \
2351 | sed -e "s|^\(<TR><TD>\)k\([0-9]*\),\([^<]*\)|\1$ba\2$lnk|" \
2352 -e "s|^\(<TR><TD>\)\([0-9]*\),\([^<]*\)|\1$b1\2$lnk|" \
2353 | _m4 -D_TITLE_="spaste(\`$tf')" \
2354 -D_SUBTITLE_="チェック後操作ボタン" \
2355 -D_FORM_="syscmd(cat)" -D_DUMPTABLE_="" \
2356 $layout/form+dump.m4.html
2357 <p>下でチェックした人を対象として:</p>
2358 <div class="foldtabs">
2359 `cgi_radio rm addteam 'id="cmteam"'`<label
2360 for="cmteam">同じチーム属性を付与</label>
2361 <div>チーム名:`cgi_text team "" 'id="inteam" list="teams"'`
2362 `cgi_datalist_h teams $allhexteams`
2363 </div>
2364 ${rmteammsg}
2365 `cgi_radio rm send id="sendmsg"`<label
2366 for="sendmsg">メッセージ送信</label>
2367 <div>
2368 `cgi_checkbox mkfrom yes 'id="mkfrom" checked'`<label for="mkfrom"
2369 >差出人を自分に(チェックを外すと相手が返事できない)</label><br>
2370 `cgi_textarea text "" cols=40`
2371 </div>
2372 ${isowner:+$cmmsg$excmsg}
2373 `cgi_radio rm close id="x"`<label for="x">×</label>
2374 </div>
2375 <h4>$grp 参加者一覧</h4>
2376 <table class="td2r">
2377 `sq $db -header -html "$sql"`
2378 </table>
2379 `cgi_hidden grp $grid`
2380 EOF
2382 crview4article() { # $1=rowid of blog, $2(optional)=extra SQL
2383 # Create TEMPORARY VIEW
2384 query<<EOF
2385 CREATE TEMPORARY VIEW writeusers AS
2386 SELECT DISTINCT author FROM article
2387 WHERE id in (
2388 select id from article where blogid=(select id from blog where rowid=$1)
2389 );
2390 CREATE TEMPORARY VIEW movablegroups AS
2391 SELECT g.rowid growid , g.gname
2392 FROM (SELECT grp.rowid, grp.gname FROM grp JOIN grp_mem gm
2393 ON grp.gname=gm.gname -- そのユーザが属している
2394 AND user='$user') g -- グループに絞る
2395 WHERE (SELECT author FROM writeusers
2396 EXCEPT
2397 SELECT user FROM grp_mem gm WHERE gm.gname = g.gname)
2398 IS NULL;
2399 $2
2400 EOF
2402 sql4readableblogs() {
2403 # Create view of blogs that can be readable to $user
2404 # Blog is readable when:
2405 # 1: blog owner is an user
2406 # 2: else, 2.1: owner-group where the $user belongs
2407 # 2.2: else, owner-group is not moderated
2408 # blog(id, author), blog_s(id, key='owner', val= ->owner)
2409 cat<<EOF ## | tee tmp/sql.out
2410 CREATE TEMPORARY VIEW readableblogs AS
2411 SELECT blog.rowid rid, id, author
2412 FROM blog
2413 NATURAL JOIN
2414 (SELECT id, val owner FROM blog_s WHERE key='owner') bs
2415 WHERE CASE WHEN (SELECT name FROM user where name=bs.owner) IS NOT NULL
2416 THEN 1 -- blog owner is an user, READABLE
2417 WHEN (SELECT val FROM grp_s
2418 WHERE gname=bs.owner AND key='regmode') = 'moderated'
2419 AND
2420 (SELECT user FROM grp_mem
2421 WHERE gname=bs.owner AND user='$user') IS NULL
2422 THEN 0
2423 ELSE 1
2424 END;
2425 EOF
2427 editheading() { # $1=rowid-of-heading
2428 rowid=${1%%[!A-Z0-9a-z_]*}
2429 if [ -z "$rowid" ]; then
2430 echo "話題番号が未指定です。" | html p
2431 return
2432 fi
2433 owner=`getvalbyid blog owner $rowid`
2434 title=`getvalbyid blog title $rowid`
2435 GF_ACTION="?blog" edittable $formdir/blog.def blog $rowid \
2436 | _m4 -D_TITLE_="修正" \
2437 -D_SUBTITLE_="[$title]@$owner" -D_DIARY_="" \
2438 -D_BLOGS_="" -D_DUMPTABLE_="" \
2439 -D_FORM_="syscmd(\`cat')" \
2440 $layout/html.m4.html $layout/form+dump.m4.html
2441 # Move to group
2442 if isuser "$owner"; then
2443 crview4article $rowid
2444 n=`query "SELECT count(*) FROM writeusers;"`
2445 ## err N=$n
2446 if [ $((n)) -gt 0 ]; then
2447 ## err ROWID=$rowid
2448 sql="SELECT growid || ':' || gname FROM movablegroups;"
2449 cat<<-EOF
2450 <div class="fold">
2451 `cgi_checkbox mv send id="mv"`<label
2452 for="mv">この話題をグループ所有に移動する</label>
2453 <div>
2454 <form action="?mvart" method="POST" enctype="multipart/form-data">
2455 移動先グループ:
2456 <select name="mv2grp">
2457 EOF
2458 query ".mode html"
2459 query<<-EOF |
2460 $sql
2461 .mode list
2462 EOF
2463 sed -e '/<\/TR>/d' -e 's,<TR>,,' -e 's,TD>,option>,g' \
2464 -e 's,n>\([0-9]*\):\(.*\)<,n value="\1">\2<,'
2465 cat<<-EOF
2466 </select>
2467 <p>(移動できるグループは、この「話題」に書き込んでいる人全てが
2468 そのグループに加入しているものに限られます)</p>
2469 <p>`cgi_checkbox cfm yes`<label>確認
2470 (この操作は元に戻すことができません)</label></p>
2471 `cgi_hidden blogrowid $rowid`
2472 `cgi_submit 移動`
2473 `cgi_reset Reset`
2474 </form>
2475 </div>
2476 </div>
2477 EOF
2478 fi
2479 # end of isuser "$owner"
2480 elif { hexteams=$(hexteams "$owner" ) # blog is of GROUP
2481 [ -n "$hexteams" ];}; then
2482 none="`echo なし|hexize`"
2483 cat<<-EOF
2484 <div class="fold">
2485 `cgi_checkbox mv2team send id="mv2team"`<label
2486 for="mv2team">この話題を以下のチームのものにする</label>
2487 <div><p>現在の所属チーム設定:
2488 `query "SELECT
2489 coalesce((SELECT val FROM blog_s
2490 WHERE id=(SELECT id FROM blog WHERE rowid=$rowid)
2491 AND key='team'),
2492 ':なし');"`</p>
2493 <form action="?mvart" method="POST" enctype="multipart/form-data">
2494 移動先チーム: `cgi_select_h mv2team $none $hexteams`
2495 <p>`cgi_checkbox cfm yes`<label>確認</label></p>
2496 `cgi_hidden blogrowid $rowid`<br>
2497 `cgi_submit 移動`
2498 `cgi_reset Reset`
2499 </form></div></div>
2500 EOF
2501 fi
2503 mvart() { # move diary to some group or team
2504 # or move blog of group to team which belong to the group
2505 blogrowid=`getpar blogrowid`
2506 cfm=`getpar cfm`
2507 ##### echo move blog:$blogrowid to $mv2grp | html p
2508 blogrowid=${blogrowid%%[!A-Z0-9a-z_]*} # Purify
2509 . ./s4-blog.sh
2510 if [ -z "$blogrowid" ]; then
2511 echo "無効な指定です(mvart)。" | html p
2512 return
2513 elif [ x"$cfm" != x"yes" ]; then
2514 echo "記事移動の確認にチェックがないので通常表示に戻ります。" | html p
2515 elif { mv2grp=`getpar mv2grp`
2516 mv2grp=${mv2grp%%[!A-Z0-9a-z_]*} # Purify
2517 [ -n "$mv2grp" ]; }; then
2518 crview4article $blogrowid
2519 ########## TRANSACTION BEGIN
2520 query "BEGIN;"
2521 n=`query "SELECT count(*) FROM writeusers;"`
2522 ## err Nwriteuser=$n
2523 if [ $((n)) -gt 0 ]; then
2524 query<<-EOF
2525 UPDATE blog_s SET val=(SELECT gname FROM grp WHERE rowid=$mv2grp)
2526 WHERE key='owner'
2527 AND id=(SELECT id FROM blog WHERE rowid=$blogrowid)
2528 AND $mv2grp IN (SELECT growid FROM movablegroups);
2529 EOF
2530 fi
2531 query "END;"
2532 ########## TRANSACTION END
2533 elif { mv2team=`getpar mv2team|sed "s/'/''/g"`
2534 [ -n "$mv2team" ];}; then
2535 # blog owner can move it to ANY team
2536 case "$mv2team" in
2537 'なし')
2538 cat<<-EOF
2539 DELETE FROM blog_s
2540 WHERE id=(SELECT id FROM blog WHERE rowid=$blogrowid)
2541 AND key='team';
2542 EOF
2543 ;;
2544 "") ;;
2545 *)cat<<-EOF
2546 BEGIN;
2547 REPLACE INTO blog_s(id, key, val)
2548 VALUES((SELECT id FROM blog WHERE rowid=$blogrowid),
2549 'team', '$mv2team');
2550 REPLACE INTO blog_s(id, key, val)
2551 VALUES((SELECT id FROM blog WHERE rowid=$blogrowid),
2552 'notify', 'all'); -- Change notify to all
2553 END;
2554 EOF
2555 esac | query
2556 fi
2557 blog_reply $blogrowid
2558 echo yes | html p
2560 editart() { # $1=article-rowid $2=blogrowid
2561 rowid=${1%%[!A-Z0-9a-z_]*}
2562 blogrowid=${2%%[!A-Z0-9a-z_]*}
2563 if [ -z "$rowid" -o -z "$blogrowid" ]; then
2564 echo "表示する記事番号が未指定です。" | html p
2565 return
2566 fi
2567 owner=`getvalbyid blog owner $blogrowid`
2568 title=`getvalbyid blog title $blogrowid`
2569 author=`getvalbyid article author $rowid`
2570 ## err EDITart: owner=$owner, author=$author
2571 if isgrpowner $user $owner; then
2572 : EDIT OK
2573 elif [ x"$owner" != x"$user" -a x"$author" != x"$user" ]; then
2574 echo "本人か所有者しか編集できません." | html p
2575 return
2576 fi
2577 aid=`query "select id from article where rowid=$rowid;"`
2578 tmpout=$tmpd/editart.$$.out
2579 GF_ACTION="?replyblog+$blogrowid#$aid" \
2580 edittable $formdir/article.def article $rowid \
2581 > $tmpout
2582 rm -f /tmp/editart.out
2583 # Cannot use pipelining to m4 with genform() because of stdin stack
2584 _m4 -D_TITLE_="コメントの修正" -D_DIARY_="" \
2585 -D_FORM_="syscmd(cat $tmpout)" \
2586 -D_SUBTITLE_="`gecos $owner`の「$title」" \
2587 -D_BLOGS_= -D_DUMPTABLE_= \
2588 $layout/html.m4.html $layout/form+dump.m4.html
2590 send2mem() {
2591 rowid=`getpar grp`
2592 if [ -z "$rowid" ]; then
2593 echo "グループが未指定です。" | html p
2594 return
2595 fi
2596 message=`getpar message`
2597 if [ -z "$message" ]; then
2598 echo "文章を入れてください。" | html p
2599 return
2600 fi
2601 grp=`getgroupbyid $rowid`
2602 members=`collectemail $grp`
2603 # smail rcpt subj (file)
2604 smail "$members" "グループ $grp 宛メッセージ(from `gecos $user`)" <<EOF
2605 $urlbase?grp+$rowid
2606 グループ $grp に所属する
2607 `gecos $user` さんよりメッセージ:
2609 $message
2610 EOF
2611 cat<<EOF
2612 <p>以下の宛先に送信しました。</p>
2613 <pre>
2614 $members
2615 </pre>
2616 <p><a href="?grp+$rowid">グループ $grp</a>に戻る。</p>
2617 EOF
2619 joingrpadmit() {
2620 # $1=yes/no $2=session-key
2621 if [ -z "$2" ]; then
2622 echo "bye bye" | html p; return
2623 fi
2624 t_usr=`session=$2 getpar user`
2625 t_grp=`session=$2 getpar group`
2626 ## err joingrpadmit: t_usr=$t_usr, t_grp=$t_grp
2627 _m4 -D_TITLE_="joingrp" $layout/html.m4.html
2628 if [ -z "$t_usr" -o -z "$t_grp" ]; then
2629 echo "無効な加入依頼です。" | html p
2630 echo "有効期限が切れたか、
2631 他の管理者がいる場合は処理済みの可能性があります。" | html p
2632 return
2633 fi
2634 if ! isgrpowner "$user" $t_grp; then
2635 echo "グループ管理者のみの機能です。" | html p; return
2636 fi
2637 case $1 in
2638 yes) joingrp "$t_grp" "$t_usr" yes ;;
2639 no) joingrp "$t_grp" "$t_usr" no ;;
2640 *)
2641 echo "無効な指定です($1)。" | html p
2642 return ;;
2643 esac
2644 gid=$(query "select rowid from grp where gname=`sqlquote $t_grp`;")
2645 rcpts="`getgroupadminmails $t_grp` $user"
2646 ## err admit: msgdir=$msgdir, rcpts="["$rcpts"]"
2647 body="グループ <a href=\"?grp+$gid\">$t_grp</a>
2648
2649 $t_usr
2650 `[ x$1 = xyes ] && echo 'を追加' || echo 'の解除操作を'`
2651 しました。"
2652 (echo "$body"; echo; echo "$url?grp+$gid") | smail "$rcpts" "joingrp $1"
2653 query "delete from session where id='$2';"
2654 echo "$body" | html p
2657 joingrprequest() {
2658 # $1=group $2=user $3=yes/no $4=email(if any $5=AsAdmin)
2659 jss="joingrp-`date +%s`-`genrandom 12`"
2660 addsession $jss +${memoplimitdays}days
2661 query "replace into par values('$jss', 'group', 'string', `sqlquote $1`),
2662 ('$jss', 'user', 'string', `sqlquote $user`);"
2663 smail "$(collectemail `getgroupadmins $1`)" "Join request to $1"<<EOF
2664 $url
2665 $user さんから
2666 グループ $1
2667 に加入依頼がありました。
2669 承認する:
2670 $urlbase?joingrpadmit+yes+$jss
2672 白紙に戻す:
2673 $urlbase?joingrpadmit+no+$jss
2674 EOF
2675 echo "管理者に加入依頼を出しました。
2676 ${memoplimitdays}日以内に加入承認操作がされれば加入できますが、
2677 グループ運用方針に懸かることですので直接の問い合わせが重要です。" | html p
2679 joingrp() {
2680 # $1=group $2=user $3=yes/no $4=email(if any $5=AsAdmin)
2681 ## err joingrp: \$1=$1 \$2=$2 \$3=$3 \$4=$4
2682 if isgrpowner "$user" "$1"; then
2683 isowner="yes"
2684 elif [ -n "$5" ]; then
2685 isowner="yes"
2686 else
2687 isowner=""
2688 fi
2689 ## err jg:isgrpowner: isowner="$isowner"
2690 if [ -n "$isowner" ]; then
2691 : # GROUP OWNER CAN DO EVERYTHING ABOUT REGISTRATION/RETIREMENT
2692 elif [ x"$2" != x"$user" ]; then # if user is not login user
2693 echo "本人か、グループ管理者しか加入操作はできません。" | html p
2694 return
2695 elif [ x"$3" = x"no" ]; then
2696 : # Do not pursue those who leave
2697 elif [ x"$3" = x"yes" ] && ismember "$user" "$grp"; then
2698 : # Member can change own email address for the joining moderated group
2699 else # adding user is $user itself
2700 case `getgroupattr $1 regmode` in
2701 moderated)
2702 joingrprequest "$@" # Request only
2703 return
2704 ;;
2705 *)
2706 ;;
2707 esac
2708 fi
2709 qgname=`sqlquote $1`
2710 cond="where gname=$qgname and user='$2'"
2711 if [ x"$3" = x"yes" ]; then
2712 query "replace into grp_mem values($qgname, '$2');"
2713 if [ -n "$4" ]; then
2714 if msg=`emaildomaincheck "$4"`; then
2715 query "replace into grp_mem_s values($qgname, '$user', 'email', \
2716 'string', '$4', NULL);"
2717 else
2718 echo $msg
2719 fi
2720 else
2721 query "delete from grp_mem_s $cond and key='email';"
2722 fi
2723 if [ -n "$5" ]; then # as ADMIN
2724 # Coming here means newly created group
2725 sql="select case\
2726 when (select count(*) from grp_mem where gname=$qgname)=1\
2727 then (select user from grp_mem\
2728 where gname=$qgname and user='$user')\
2729 else '' end; "
2730 err NewGrpChk: $sql
2731 if [ -n "`query \"$sql\"`" ]; then
2732 ## err ADMIN: "replace into grp_adm values($qgname, '$user');"
2733 query "replace into grp_adm values($qgname, '$user');"
2734 fi
2735 fi
2736 else
2737 query "delete from grp_mem $cond;
2738 delete from grp_mem_s $cond;
2739 delete from grp_mem_m $cond;"
2740 fi
2742 grp_add_team() (
2743 # $1=grp-rowid $2=team $3...=user-rowid(s)
2744 grp=`getgroupbyid $1`
2745 team=$2; shift; shift
2746 [ -z "$grid" -o -z "$team" -o -z "$1" ] && return
2747 { echo "BEGIN;"
2748 for user; do
2749 echo "REPLACE INTO grp_mem_m(gname, user, key, type, val) VALUES(\
2750 '$grp',\
2751 (SELECT name FROM user WHERE rowid=$user),\
2752 'team', 'string', '$team');"
2753 done
2754 echo "END;"
2755 } | query
2757 grp_rm_team() (
2758 # $1=grp-rowid $2=team $3...=user-rowid(s)
2759 grid=$1
2760 qgrp=$(sqlquote "`getgroupbyid $grid`")
2761 team=$2; shift; shift
2762 [ -z "$grid" -o -z "$team" ] && return
2763 { echo "BEGIN;"
2764 for user; do
2765 echo "DELETE FROM grp_mem_m\
2766 WHERE gname=$qgrp \
2767 AND user=(SELECT name FROM user WHERE rowid=$user)\
2768 AND key='team' AND val='$team';"
2769 done
2770 cat<<-EOF
2771 DELETE FROM blog_s
2772 WHERE rowid=(
2773 SELECT rowid
2774 FROM blog_s a
2775 WHERE key='team'
2776 AND id IN (SELECT id FROM blog_s WHERE key='owner' AND val=$qgrp)
2777 AND NOT EXISTS (SELECT * FROM grp_mem_m
2778 WHERE key='team' AND val=a.val -- a.val=team
2779 AND gname = (SELECT val FROM blog_s b
2780 WHERE a.id=b.id AND key='owner')
2781 ));
2782 EOF
2784 echo "END;"
2785 } | query
2787 grp_reg_adm() {
2788 # $1=grp-rowid $2...=user-rowid
2789 grid=$1
2790 grp=`getgroupbyid "$1"`
2791 if [ -z "$grp" ]; then
2792 echo "無効なグループIDです" | html p; return
2793 fi
2794 if ! isgrpowner $user "$grp"; then
2795 echo "$grp グループの管理者しかこの操作はできません。" | html p; return
2796 fi
2797 shift
2798 for urid; do
2799 newadm=`query "select name from user where rowid=$urid;"`
2800 if [ -z "$newadm" ]; then
2801 echo "指定ユーザIDがおかしいようです。" | html p; return
2802 fi
2803 err GRP_reg_adm: "replace into grp_adm values(`sqlquote $grp`, '$newadm');"
2804 err ismember $newadm $grp
2805 if ismember $newadm $grp; then
2806 # OK, go ahead
2807 getgname="(select gname from grp where rowid=$grid)"
2808 query "replace into grp_adm values($getgname, '$newadm');"
2809 # confirm insertion
2810 sql="select * from grp_adm where gname=$getgname and user='$newadm'"
2811 if [ -n "`query \"$sql;\"`" ]; then
2812 echo "追加完了: $newadm" | html p
2813 else
2814 echo "追加失敗($1 $urid)" | html p
2815 fi
2816 fi
2817 showgroup $grid
2818 done
2820 dumptable() {
2821 # $1=mode $2=Table $3=column-list-of-*_s(defaults to *) $4=conditions(if any)
2822 # textのフィールドだけ全てダンプにしたほうがいいか
2823 # $DT_VIEW sets link
2824 # 6/17の次: editリンクじゃなくてスレッドVIEWリンクでいいんちゃう?
2825 ### elink="<a href=\"$myname?edittable+$2+\\2\">EDIT</a>"
2826 VIEW=${DT_VIEW-replyblog}
2827 if [ -n "$VIEW" ]; then
2828 dvlink=" <a href=\"$myname?$VIEW+\\2\\3\">VI</a><a href=\"$myname?$VIEW+\\2#bottom\">EW</a>"
2829 fi
2830 # $DT_CHLD=ChildTable:BindColumn
2831 if [ -n "$DT_CHLD" ]; then
2832 _t=${DT_CHLD%:*} _i=${DT_CHLD#*:}
2833 cntall="(select count($_i) from $_t where $_i=a.id)"
2834 # XXX: Dirty workaround for slow subquery of acclog
2835 presql="CREATE TEMPORARY TABLE IF NOT EXISTS myacclog AS
2836 SELECT * FROM acclog WHERE user='$user' and tbl='$2';"
2837 cntnew="(select count(val) from ${_t}_s where key='ctime' \
2838 and id in (select id from $_t where $_i=a.id) \
2839 and val > coalesce((select time from myacclog where \
2840 tblrowid=a.rowid),\
2841 '1970-01-01'))"
2842 cnt="$cntnew as '新着', $cntall as '総数',"
2843 dt_class=" td2r td3r dumpblogs"
2844 fi
2845 # Construct join expression
2846 eav="" scols=""
2847 pk=`gettblpkey $2`
2848 substr=${dumpcollen:+"substr(val, 0, $dumpcollen)"}
2849 substr=${substr:-val}
2850 for col in ${3:-`gettbl_s_cols $2`}; do
2851 case $col in
2852 gecos) scols="$scols${scols:+, }${col#}"
2853 continue ;; # built-in column name
2854 *:*) col=${col%:*} as=${col#*:} ;;
2855 *) as=${col} ;;
2856 esac
2857 eav=$eav${eav:+,}" max(case key when '$col' then $substr end) as $as"
2858 scols="$scols${scols:+, }b.$as"
2859 done
2860 #case author when '$user' then a.rowid else '---' end as ID,
2861 sql=${DT_SQL:-"select \
2862 a.rowid as LINK,\
2863 $cnt\
2864 $scols from $2 a left join\
2865 (select $pk,$eav,
2866 max(case key when 'owner'
2867 then (SELECT gecos FROM gecoses WHERE name=val) END) as gecos
2868 from ${2}_s c group by $pk) b on a.$pk=b.$pk $4;"}
2869 ## err dt:SQL="`echo \"$presql$sql\"|tr -d '\n'`"
2870 cat<<EOF | sed "s,\(<TR><TD>\)\([1-9][0-9]*\)\(#[0-9a-fxs]*\)*</TD>,\1$elink$dvlink</TD>,"
2871 <div> <!-- for folding by check button (s4-funcs.sh:dumptable()) -->
2872 <div class="dumptable">
2873 <table class="b$dt_class">
2874 `sq -header -cmd ".mode $1" $db "$presql$sql"`
2875 </table>
2876 </div> <!-- dumptable -->
2877 </div> <!-- for folding by check button (s4-funcs.sh:dumptable()) -->
2878 EOF
2881 par2table() (
2882 # copy current parameters of par into destination table
2883 # $1=definition-file
2884 # Using $user and $session
2885 # Return value:
2886 # 0: Stored successfully
2887 # 1: Insufficient fillings
2888 # 2: No permission to modify the record
2889 # 3: Invalid rowid
2890 # 4: SUCCESS to delete
2891 # 5: Stop deletion for lack of confirm check
2892 # 6: Password length too short
2893 # 7: Password mismatch
2894 # 8: Old password incorrect
2895 rowid=`getpar rowid`
2896 if [ ! -e $1 ]; then
2897 echo "テーブル定義ファイルが見付かりません" | html p
2898 exit 1
2899 fi
2900 tbl=${1%.def}
2901 tbl=${tbl##*/}
2902 if [ -n "$rowid" ]; then # Modify existing entry
2903 if [ x"$tbl" = x"user" ]; then
2904 rowowner=`query "select name from $tbl where rowid=$rowid;"`
2905 elif [ x"$tbl" = x"grp" ]; then
2906 sql="select gname from $tbl where rowid=$rowid;"
2907 ##err p2t:grp:q $sql
2908 isgrpowner $user "`query $sql`" && rowowner=$user
2909 else
2910 rowowner=`query "select owner from $tbl where rowid=$rowid;"`
2911 rowowner=${rowowner:-`query "select author from $tbl
2912 where rowid=$rowid;"`}
2913 fi
2914 ### err rowowner=$rowowner
2915 if [ x"$user" != x"$rowowner" ]; then
2916 echo "他人のレコードはいじれないの" | html p
2917 return 2
2918 elif [ -z "$rowowner" ]; then
2919 echo "指定したレコードはないみたい" | html p
2920 return 3
2921 fi
2922 rm=`getpar rm` cfm=`getpar confirm`
2923 # Editing existent entry
2924 if [ x"$rm" = x"yes" ]; then
2925 if [ x"$rm$cfm" = x"yesyes" ]; then
2926 query "delete from $tbl where rowid=$rowid;"
2927 return 4
2928 else
2929 echo "消去確認のチェックがないので消さなかったの..." | html p
2930 return 5
2931 fi
2932 fi
2933 fi
2935 ts=${tbl}_s tm=${tbl}_m val="" pval="" formaster=""
2936 if [ -n "$rowid" ]; then
2937 # Update of existing record
2938 for col in `gettblcols $tbl`; do
2939 val=`getparquote $col`
2940 [ -z "$val" ] && continue
2941 ## err query "update $tbl set $col=$val where rowid=$rowid"
2942 ## XX: THIS IS DIRTY hack to ensure non-foreign key in blog_s
2943 sql="update $tbl set $col=$val where rowid=$rowid;"
2944 if [ x"$tbl" = x"grp" -a x"$col" = x"gname" \
2945 -o x"tbl" = x"user" -a x"$col" = x"name" ]; then
2946 ## User name cannot be changed with interface provided with this
2947 ## script. But we offer the trigger to change owner user
2948 ## of blog_s table.
2949 #err "select quote($col) from $tbl where rowid=$rowid;"
2950 old=`query "select quote($col) from $tbl where rowid=$rowid;"`
2951 cat<<-EOF | query
2952 BEGIN;
2953 $sql
2954 update blog_s set val=$val
2955 where key='owner' and val=$old;
2956 COMMIT;
2957 EOF
2958 ## XX: DIRTY Hack Ends here
2959 ## We should keep blog's owner as a single column which has
2960 ## foreign key constraint with primary key of grp/user.
2961 else
2962 query "$sql"
2963 fi
2964 done
2965 # Then, set up $pval for further insertion of tbl_s and tbl_m
2966 for col in `gettblpkey $tbl`; do
2967 val=`query "select $col from $tbl where rowid=$rowid;"|sed -e 's/\"/\"\"/g'`
2968 pval="$pval${pval:+, }\"$val\""
2969 done
2970 else
2971 # New entry
2972 # Generate values() for primary keys
2973 for col in `gettblpkey $tbl`; do
2974 # Genuine primary keys for _m and _s
2975 val=`getvalquote $tbl $col`
2976 [ -z "$val" ] && continue
2977 pval="$pval${pval:+, }$val"
2978 done
2979 ##err pval=$pval
2980 for col in `gettblfkey $tbl`; do
2981 # args for values() to insertion into master table
2982 val=`getvalquote $tbl $col`
2983 [ -z "$val" ] && continue
2984 formaster=$formaster"${formaster:+, }$val"
2985 done
2986 formaster="$pval${formaster:+, }$formaster"
2987 ## err formaster=$formaster
2988 if [ -z "$formaster" ]; then
2989 echo "項目を全て埋めてください" | html pre
2990 return 1
2991 fi
2992 ## err "replace into $tbl values($formaster);"
2993 query "replace into $tbl values($formaster);"
2994 ## Insertion to master table, done
2995 fi
2997 for kt in s m; do
2998 tb2=${tbl}_$kt
2999 for col in `gettbl_${kt}_cols $tbl`; do
3000 ptype=`getpartype $col "limit 1"`
3002 # First, check update of existing entries in _m
3003 if [ $kt = m ]; then
3004 # sessID|address.1.22|string|Somewhere-x.y.z
3005 sql=""
3006 ##err dots from query "select var from par where var like '$col.%';"
3007 for v in `query "select var from par where var like '$col.%';"`; do
3008 # v=address.1.22
3009 st_rowid=${v##*.}
3010 origcol=${v%%.*} # original column derived from
3011 ##err Updating for $v st_rowid=$st_rowid, partype=`getpartype $v`
3012 ##case `getpartype $v` in
3013 ## err CASE `gettbl_coltype $tbl/$origcol` in
3014 ## err edit flag = `getpar action.$v`
3015 case `getpar action.$v` in
3016 rm)
3017 if [ x`getpar confirm.$v` = x"yes" ]; then
3018 newsql="delete from $tb2"
3019 else
3020 echo "削除確認未チェック" | html p
3021 fi ;;
3022 edit)
3023 case `gettbl_coltype $tbl/$origcol` in
3024 image|document|binary)
3025 file=$tmpd/`getparfilename $v`
3026 ## err type=file=$file
3027 [ -z "$file" ] && continue
3028 bn=`sqlquotestr "${file##*/}"`
3029 bin="X'"$(hexize "$file")"'"
3030 ct=`file --mime-type - < "$file" |cut -d' ' -f2`
3031 type=\"file:$ct\"
3032 newsql="update $tb2 set val=$bn, type=$type, bin=$bin"
3033 cachedir=`getcachedir "$tbl/$rowid"`
3034 err getcache tbl/rowid=$tbl/$rowid, rm -r $cachedir
3035 rm -rf $cachedir
3036 ;;
3037 *)
3038 newsql="update $tb2 set val=(select val from par where var \
3039 like '$col.%.$st_rowid')"
3040 ;;
3041 esac
3042 ;;
3043 *) # maybe "keep", do not modify value
3044 continue
3045 ;;
3046 esac
3047 # err newsql=$newsql
3048 sql=$sql$nl"$newsql where rowid=$st_rowid;"
3049 done
3051 if [ x"$bin" = x"NULL" ]; then
3052 ## err repl:normal sql=`echo $sql`
3053 query "$sql
3054 delete from $tb2 where type='string' and val='';"
3055 ## err repl:normal done
3056 else
3057 sqlfile="$tmpd/sqlf.$$"
3058 echo "$sql" > $sqlfile
3059 ## err repl:sqlfile=`ls -lF $sqlfile`
3060 query ".read $sqlfile"
3061 ## err repl:done
3062 fi
3063 # Rest of kt==m: set multiple mode
3064 nr=`getparcount $col`
3065 else
3066 nr=1 # for kt==s, number of records is 1
3067 fi
3069 i=0
3070 while [ $i -lt $nr ]; do
3071 limit="limit 1 offset $i"
3072 i=$((i+1)) # increase beforehand against continue
3073 val=`getvalquote $tbl $col "$limit"`
3074 [ -z "$val" -o x"$val" = x'""' -o x"$val" = x"NULL" ] && continue
3075 ## err $col=$val
3076 bin=NULL
3077 ## err partype$col=`getpartype $col "$limit"`
3078 case $ptype in
3079 file) file=$tmpd/`getparfilename $col "$limit"`
3080 ## err parfile-$col=$file
3081 [ -z "$file" ] && continue
3082 bin="X'"$(hexize "$file")"'"
3083 ct=`file --mime-type - < "$file"|cut -d' ' -f2`
3084 type=\"file:$ct\" ;;
3085 "*"*) continue ;; # foreign table
3086 *) type=\"string\" ;;
3087 esac
3088 case `gettbl_coltype $tbl/$col` in
3089 password) # special care for password
3090 # name={password,pswd1,pswd2}
3091 p1=`getpar pswd1 "$limit"`
3092 if [ -z "$p1" ]; then
3093 continue # SKIP password setting, if p1 is empty
3094 else
3095 pswd=`getpar pswd "$limit"` p2=`getpar pswd2 "$limit"`
3096 ## err pswd=$pswd
3097 if pwcheck "$pswd"; then
3098 if [ x"$p1" = x"$p2" ]; then
3099 case "$p1" in
3100 ??????????*) ;;
3101 *) echo "パスワードは10字以上にしてください。" | html p
3102 return 6;;
3103 esac
3104 val="\"`echo $p1|mypwhash`\""
3105 else
3106 echo "2つの新パスワード不一致" | html p
3107 return 7
3108 fi
3109 else
3110 echo "旧パスワード違います" | html p
3111 return 8
3112 fi
3113 fi
3114 ;;
3115 esac
3116 ## err p2t: "replace into $tb2 values($pval, \"$col\", $type, $val, bin...);"
3117 #query "replace into $tb2 values($pval, \"$col\", $type, $val, $bin);"
3118 sql="replace into $tb2 values($pval, \"$col\", $type, $val, $bin);"
3119 if [ x"$bin" = x"NULL" ]; then
3120 ## err Normal-query: `echo $sql`
3121 query "$sql"
3122 else
3123 sqlfile="$tmpd/query.$$"
3124 echo "$sql" > $sqlfile
3125 ## err sqlfile=`ls -lF $sqlfile`
3126 query ".read $sqlfile"
3127 fi
3128 ## err p2t done
3129 done
3130 done
3131 done
3132 return 0
3133 ##err donee
3135 genform() {
3136 # $1 = form definition file
3137 # $2, $3 (optional)= table name and ROWID
3138 # If $GF_VIEWONLY set and nonNull, output values without form
3139 # If $GF_ARGS set, use it as content-strings in the form
3140 # If $GF_OWNER set, use it as value of name="owner"
3141 # If $GF_STAGE set, use it as value of name="stage"
3142 forms="" hiddens="" rowid=$3
3143 if [ ! -e "$1" ]; then
3144 echo "そのようなデータベースはないようです($2)。" | html p
3145 return
3146 elif [ -n "$2" ]; then
3147 rec=`query "select * from $2 where rowid='$rowid';"`
3148 if [ -z "$rec" ]; then
3149 pk=`gettblpkey $2`
3150 ###rec=`sq $db "select rowid from $2 where $pk='$rowid'"`
3151 rec=`query "select rowid from $2 where $pk='$rowid';"`
3152 rowid=$rec
3153 rec=$3
3154 fi
3155 if [ -z "$rec" ]; then
3156 echo "そんなレコードはないみたいね..." | html p
3157 return
3158 fi
3159 fi
3160 if [ -z "$GF_VIEWONLY" ]; then
3161 rm='<input id="rm" name="rm" type="checkbox"
3162 value="yes"><label for="rm">このエントリの削除</label>
3163 <span>ほんとうに消しますよ(確認)!
3164 <input name="confirm" type=checkbox value="yes">はい</span>'
3165 fi
3166 # Image Cache dir
3167 ## err genform: getcache=$2/$rowid
3168 td=`getcachedir "$2/$rowid"`
3169 while IFS=: read prompt name keytype type args; do
3170 [ -z "${prompt%%\#*}" ] && continue # skip comment line(#)
3171 sp="${args:+ }"
3172 form="" val=""
3173 if [ -n "$rowid" ]; then
3174 # err genform2a: Seeking for "$2.$name, type=$type"
3175 rawval=`getvalbyid $2 $name $rowid $td`
3176 val=`echo "$rawval"|htmlescape`
3177 ## err genform3a: getvalbyid $2 $name $rowid $td
3178 ## err genform3b: val="[$val]" type="$type"
3179 fi
3180 if [ -n "$GF_VIEWONLY" ]; then
3181 is_hidden "$2" "$name" && continue
3182 fi
3183 case "$type" in
3184 text*)
3185 cgiform=cgi_multi_$type
3186 if [ -s $td/$name.count -a -n "$val" ]; then
3187 form=`$cgiform $name $td`
3188 val=$(echo "$val"|
3189 while read fn; do
3190 echo "<tr><td>`cat $td/$fn|htmlescape|hreflink`
3191 </td></tr>$nl"
3192 done)
3193 val="<table>$nl$val$nl</table>"
3194 else
3195 #form="<input name=\"$name\" value=\"$val\" type=\"$type\"$sp$args>$nl"
3196 form=`cgi_$type $name "$rawval" "$args"`
3197 fi
3198 ;;
3199 [Rr][Aa][Dd][Ii][Oo])
3200 fh="<label><input type=\"radio\" name=\"$name\""
3201 form="`echo $args|sed -e \
3202 \"s,\([^ =][^=]*\)=\([^= ][^= ]*\),$fh value=\\"\2\\">\1</label>,g\"`"
3203 ;;
3204 [Cc][Hh][Ee][Cc][Kk][Bb][Oo][Xx])
3205 form="<label><input type=\"checkbox\" name=\"$name\" value=\"${args#*=}\">${args%=*}</label>"
3206 ;;
3207 [Ss][Ee][Ll][Ee][Cc][Tt])
3208 fh="<select name=\"$name\">$nl"
3209 form=$(for l in $args; do
3210 echo "<option value=\"${l#*=}\">${l%=*}</option>"
3211 done)
3212 if [ -n "$val" ]; then
3213 form=`echo $form|sed -e "s,\(value=.$val.\),\\1 selected,"`
3214 fi
3215 form="$fh$form</select>"
3216 ;;
3217 [Ii][Mm][Aa][Gg][Ee]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Bb]inary)
3218 if [ -s $td/$name.count ]; then
3219 form=`cgi_multi_file $name $td "$args"`
3220 if [ -n "$val" ]; then
3221 hrfb="$myname?showattc+$2_m"
3222 val=$(echo "$rawval" \
3223 | while read fn; do
3224 data=`percenthex "$td/$fn"`
3225 #ct=`cat $td/$fn.content-type`
3226 ct=`file --mime-type - < "$td/$fn"|cut -d' ' -f2`
3227 ri=`cat "$td/$fn.rowid"`
3228 ## err fn=$fn, name=$name, ri=$ri; ls -lF "$td/" 1>&3
3229 #imgsrc="<img src=\"data:$ct,$data\">"
3230 #echo "<a href=\"$hrfb+$ri\">$imgsrc</a><br>"
3231 iconhref "$td/$fn" "$hrfb+$ri" ""
3232 done)
3233 fi
3234 else
3235 form="<input type=\"file\" name=\"$name\" $args>"
3236 if [ -n "$val" ]; then
3237 imgs=$(echo "$rawval"\
3238 |while read fn;do
3239 data=`percenthex "$td/$fn"`
3240 echo "<img src=\"data:image/png,$data\">$fn<br>"
3241 done)
3242 form=$form"<br>$imgs"
3243 val=$imgs # 2015-06-15
3244 else
3245 form="<input type=\"file\" name=\"$name\" $args>"
3246 fi
3247 fi
3248 ;;
3249 [Hh][Ii][Dd][Dd][Ee][Nn])
3250 if [ -n "$GF_STAGE" -a x"$name" = x"stage" ]; then
3251 args="value=\"$GF_STAGE\""
3252 fi
3253 form="<input type=\"hidden\" name=\"$name\" $args>"
3254 prompt='' # Remove prompt
3255 ;;
3256 [Aa][Uu][Tt][Hh][Oo][Rr])
3257 form="<input type=\"hidden\" name=\"author\" value=\"$user\">"
3258 prompt="" ;;
3259 [Oo][Ww][Nn][Ee][Rr])
3260 val=${GF_OWNER:-$val}
3261 val=${val:-$user}
3262 form="<input type=\"hidden\" name=\"owner\" value=\"$val\">"
3263 prompt="" ;;
3264 [Uu][Ss][Ee][Rr])
3265 # XXX: is null $user ok?
3266 #form="<input type=\"hidden\" name=\"user\" value=\"$user\">"
3267 [ -n "$GF_VIEWONLY" ] && continue
3268 form="$user"
3269 ;;
3270 [Pp]assword)
3271 [ -n "$GF_VIEWONLY" ] && continue
3272 form="`cgi_passwd`"
3273 val=""
3274 ;;
3275 [Ss][Ee][Rr][Ii][Aa][Ll]|[Ss][Tt][Aa][Mm][Pp])
3276 if [ -z "$rowid" ]; then
3277 val=`genserial`
3278 fi
3279 form="<input type=\"hidden\" name=\"$name\" value=\"$val\">"
3280 prompt="" ;;
3281 [Ss][Ee][Ss][Ss][Ii][Oo][Nn])
3282 prompt=""
3283 ;;
3284 parent|path|blog*)
3285 prompt=""
3286 ;;
3287 "*"*)
3288 tail=$tail"``"
3289 continue ;;
3290 esac
3291 if [ -n "$prompt" ]; then
3292 if [ -n "${GF_VIEWONLY}" ]; then
3293 form=$val
3294 else
3296 fi
3297 forms=$forms" <tr class=\"$name\"><th>$prompt</th><td>$form</td></tr>$nl"
3298 else
3299 hiddens=$hiddens$nl"$form"
3300 fi
3301 done < $1
3302 # enctype="multipart/form-data"
3303 cat<<EOF
3304 <form action="${GF_ACTION:-$myname}" method="POST" enctype="multipart/form-data">
3305 ${rowid:+$rm}
3306 <table class="b $2">
3307 $forms
3308 </table>$hiddens
3309 ${GF_STAGE:+`cgi_hidden stage $GF_STAGE`}
3310 ${rowid:+<input type="hidden" name="rowid" value="$rowid">}
3311 EOF
3312 if [ -z $GF_VIEWONLY ]; then
3313 cat<<EOF
3314 <input type="submit" name="sub" value="OK">
3315 <input type="reset" name="res" value="Reset">
3316 EOF
3317 fi
3318 cat<<EOF
3319 $GF_ARGS</form>
3320 $tail
3321 EOF
3323 edittable() {
3324 # $1=form-def $2=table $3 rowid
3325 genform "$@"
3327 viewtable() {
3328 GF_VIEWONLY=1 genform "$@"
3330 showattc() {
3331 # $1=table_m $2=rowid &optional $3=RawFlag
3332 ## err \$1=$1 \$2=$2 \$3=$3
3333 if ! isfilereadable $user $1 $2; then
3334 contenttype; echo
3335 echo "このファイルは管理者のみしか見られません" | html p
3336 putfooter; exit
3337 fi
3338 idir=`umask 002; mktempd` || exit 1
3339 # tmpfiles=$tmpfiles"${tmpfiles+ }$idir"
3340 bin=$idir/$myname-$$.bin
3341 sql="select quote(bin) from $1 where rowid='$2';"
3342 ## err showattc: sql: $sql
3343 sq $db "$sql" | unhexize > $bin
3344 tv=`query "select type||'//'||val from $1 where rowid='$2';"`
3345 type=${tv%//*} fn=${tv#*//}
3346 ## err tv=$tv type=$type fn=$fn, tp2=${tv%\|*}
3347 ct=${type#file:}
3348 case $ct in # all text/* changed to text/plain
3349 text/*)
3350 charset=`nkf -g $bin|cut -d' ' -f1`
3351 case $charset in
3352 ASCII*) charset="" ;;
3353 esac
3354 if [ -z "$3" ]; then
3355 ct="text/html${charset:+; charset=$charset}"
3356 link="?showattc+$1+$2+raw"
3357 cat $bin | htmlescape \
3358 | sed 's,^,<span></span>,' \
3359 | _m4 -D_TITLE_="$fn" -D_CONTENT_TYPE_="$ct" \
3360 -D_LINK_="$link" \
3361 -D_BODY_="syscmd(\`cat')" $layout/pretty.m4.txt
3362 exit $?
3363 fi
3364 ct="text/plain${charset:+; charset=$charset}"
3365 ;;
3366 esac
3367 contenttype "$ct"
3368 echo "Content-Disposition: filename=\"$fn\""
3369 echo "Content-Length: " `cat $bin | wc -c`; echo
3370 #echo "Content-Type: " ${type#file:}; echo
3371 cat $bin
3374 # Some default stupid handler on CGI values
3376 default_storedb() {
3377 # ARG: $1=table-def-file
3378 # RET: $tbl=table-name, $col=mail-column, $cols=columns
3379 tbl=`basename $1`
3380 tbl=${tbl%.def}
3381 cols="`grep :text $1|cut -d: -f2`"
3382 col=`echo "$cols"|head -1`
3383 vcol=`getpar $col`
3384 err default0: \$1=$1 col=$col cols="[$cols]" vcol=$vcol
3385 if [ -n "$vcol" ]; then
3386 par2table $1
3387 else
3388 return 2 # No insertion occurred
3389 fi
3392 default_view() { # $1=def-file
3393 ### DT_VIEW="edittable+$tbl" dumptable html $tbl "$cols" \
3394 ## DT_VIEW="edittable+$tbl" dumptable html $tbl "name memo file" \
3395 default_storedb "$@"
3396 query "select rowid from $tbl order by rowid desc;" \
3397 | while read rowid; do
3398 viewtable $1 $tbl $rowid
3399 done | _m4 -D_TITLE_="$tbl" \
3400 -D_FORM_="`genform $1`" \
3401 -D_DUMPTABLE_="syscmd(cat)" \
3402 $layout/html.m4.html $layout/form+dump.m4.html
3404 default_viewtext() { # $1=def-file
3405 ### DT_VIEW="edittable+$tbl" dumptable html $tbl "$cols" \
3406 default_storedb "$@"
3407 DT_VIEW="viewtable+$tbl" dumptable html $tbl "name memo file" \
3408 | _m4 -D_TITLE_="$tbl" \
3409 -D_FORM_="`genform $1`" \
3410 -D_DUMPTABLE_="syscmd(cat)" \
3411 $layout/html.m4.html $layout/form+dump.m4.html
3413 default_smail() {
3414 default_storedb "$@"
3415 if [ $? -eq 2 ]; then
3416 _m4 -D_TITLE_="入力" \
3417 -D_FORM_="`genform $1`" \
3418 -D_DUMPTABLE_="" \
3419 $layout/html.m4.html $layout/form+dump.m4.html
3420 return
3421 fi
3422 cond=""
3423 for pk in `gettblpkey $tbl`; do
3424 pv=$(sqlquote $(getpar $pk))
3425 cond="$cond${cond:+ and }$pk=$pv"
3426 done
3427 sql="select rowid from $tbl where $cond;"
3428 rowid=`query "$sql"`
3429 ## err smail1 - "$sql" "-> rowid=$rowid"
3431 while IFS=: read prompt name keytype type args; do # Read from $1
3432 val=`getpar $name`
3433 if [ -n "$val" ]; then
3434 text="$text
3435 $prompt
3436 $name=$val
3437 ---------------------------------------------------------"
3438 fi
3439 case "$type" in
3440 image|document|file)
3441 fn="`getvalbyid $tbl $name $rowid $tmpd`"
3442 fns=$(echo "$fn"|while read fn; do
3443 err mv $tmpd/$fn.orig $tmpd/$fn
3444 mv $tmpd/$fn.orig $tmpd/$fn
3445 rm $tmpd/$fn.rowid # Remove cache flag
3446 ## err "`ls $tmpd/$fn`"
3447 echo $fn
3448 done)
3449 files="$files $fns"
3450 ;;
3451 esac
3452 done < $1
3453 ## err FILES=$files "`ls -lF $tmpd`"
3454 subj="from ${REMOTE_ADDR}"
3455 (echo "$url"
3456 echo "への書き込みがありました。"
3457 echo "------"
3458 echo "$text"
3459 ) | (cd $tmpd &&
3460 err LS="`ls -lF`" &&
3461 $mydir/sendmultipart.sh -t "$admin" -s "$subj" $files)
3462 _m4 -D_TITLE_="入力完了" $layout/html.m4.html
3463 echo "以下の内容で送信しました。" | html p
3464 viewtable $1 $tbl \
3465 `query "select rowid from $tbl order by rowid desc limit 1;"`
3466 echo "戻る" | html a "href=\"?\""