s4

view s4-blog.sh @ 879:188ad98d443f

Do not add prefix ">" to subscription notification.
author HIROSE Yuuji <yuuji@gentei.org>
date Wed, 09 Dec 2020 11:51:41 +0900
parents 85a20c87bf97
children 4e755437dbcb
line source
1 #
2 type cgiinit >/dev/null 2>&1 || . ./s4-funcs.sh
4 # Global error flags
5 BLOG_NOTMEM=1
6 BLOG_FROZEN=2
7 FROZEN_TAG='<span class="frozen">[凍結]</span>'
9 blog_genform() {
10 #
11 t=$1
12 }
14 blog_writable() (
15 # $1=articleid $2=user
16 # Return: $?=0 - Writable
17 # =1 - NOT Writable because user is not a member
18 # =2 - NOT Writable because blog is frozen
19 blogowner=`getvalbyid blog owner "$1"`
20 state=`getvalbyid blog state "$1"`
21 rc=0
22 [ x"$blogowner" = x"$2" ] || isuser "$blogowner" || ismember "$2" "$blogowner" || rc=$((rc+$BLOG_NOTMEM))
23 [ "$state" = "frozen" ] && rc=$((rc+$BLOG_FROZEN))
24 return $rc
25 )
26 blog_readable() {
27 # $1=articleid $2=user
28 mode=`getgroupattr "$grp" regmode`
29 }
30 blog_getteam() {
31 # $1=rowid of blog
32 blogid="${1%%[!A-Z0-9a-z_]*}"
33 # team cannot get `getvalbyid blog team "$blogid"` because it's not
34 # defined in blog.def. Yes, it is Illegal USE!!
35 query "SELECT val FROM blog_s
36 WHERE id=(SELECT id FROM blog WHERE rowid=$blogid)
37 AND key='team';"
38 }
39 blog_notify_reply() (
40 # $1=blogid $2=ReplyingUser $3=WrittenText $4(optional)=Action
41 blogid="${1%%[!A-Z0-9a-z_]*}"
42 blogowner=`getvalbyid blog owner "$blogid"`
43 blogtitle=`getvalbyid blog title "$blogid"`
44 blogurl="$urlbase?replyblog+$blogid"
45 action=${4:-書き込み}
46 mode=`getvalbyid blog notify "$blogid"`
47 isgroup "$blogowner" && _isgroup=true || _isgroup=false
48 ### EXCEPT=`sqlquote "$user"` ## User should receive to feal some annoyance
49 case $mode in
50 admin)
51 if $_isgroup; then
52 emails=`getgroupadminmails "$blogowner"`
53 else
54 emails=`collectemail "$blogowner"`
55 fi
56 notifyto=`getpar notifyto`
57 if [ -n "$notifyto" ]; then
58 emails=$emails" `email4groupbyuid \"$blogowner\" $notifyto`"
59 fi
60 ;;
61 no|"") emails="" ;; # 2020-0630 Omit email when heavy load...(XXX)
62 *) team=`blog_getteam "$blogid"`
63 # team cannot get by `getvalbyid blog team "$blogid"`
64 emails=`TEAM=$team collectemail "$blogowner"` ;;
65 esac
66 ## 2017-0210 Respond to the direct reply mark such as: >#1234
67 replymark=`echo "$3"|nkf -w -Z0|grep '^ *>#'`
68 authgecos=`gecos $2`
69 ## 2020-1209 If the first line begins with '## ', use it as Subject
70 firstline=`echo "$3"|head -1|nkf -w -Z0`
71 if [ -z "$4" ]; then
72 if [ -n "$replymark" ]; then
73 # If the action is new subscription($4="") and has ">#123" marks...
74 ids=`echo "$replymark"|sed 's/[^#0-9]*#\([0-9]*\)[^#0-9]*/\1 /g'`
75 ids=`echo $ids|tr -dc '[0-9 ]'|tr ' ' ','`
76 # -> 123,345,347
77 unames=`query "SELECT distinct author FROM article \
78 WHERE rowid in ($ids)\
79 AND blogid=(SELECT id FROM blog WHERE rowid=$blogid);"`
80 if [ -n "$unames" ]; then
81 e4g=$(if $_isgroup; then
82 email4group "$blogowner" $unames
83 else
84 for u in $unames; do
85 collectemail $u
86 done
87 fi)
88 emails=$emails" $e4g"
89 for e in $unames; do
90 g=`gecos $e`
91 whom=$whom"${whom:+,}${g:-$e}さん"
92 done
93 action="${whom}への返信"
94 fi
95 elif echo "$firstline" | grep -q "^## "; then
96 subject=${firstline#\#\# }
97 fi
98 else
99 # This else block is not symmetry, check later(2020-1209)
100 [ x"$2" = x"$blogowner" ] && return # If author=blogowner, unnecessary
101 fi
102 test -z "$emails" && return
103 sj=${subject:-${action}通知}
104 err notify: user=$user Admins=`getgroupadmins "$blogowner"` Mode=$mode Emails="[$emails]"
105 quotedowner=`echo $blogowner | nkf -jM | tr -d '\n"'`
106 MAIL_FROM=$noreply_from \
107 SMAIL_TO="\"$quotedowner\" readers <$noreply>" \
108 smail "$emails" "$sj $urlbase"<<EOF
109 [$blogtitle]板に${action}がありました。
110 ※※※このメイルには返信できません(返信は次のURLへ)※※※
111 場所: $blogurl (返信先)
112 所有: $blogowner
113 題目: $blogtitle
114 筆者: $authgecos
115 内容:
116 `echo "$3"|sed 's/^/ /'`
118 ※※このメイルに返信しても通知者には伝わりません。
119 ※※上記URLから${S4NAME:-s4}掲示板に書き込んでください。
120 EOF
121 )
123 blog_showentry() {
124 # $1=table $2=rowid $3(optional)=control-sequence
125 # if [ -n "$2" ]; then
126 # if [ -n "$imgcached" ]; then
127 # bstmpdir=$tmpdir/$imgcached/$thumbxy
128 # else
129 # bstmpdir=$tmpd
130 # # tmpd=`mktempd`
131 # # tmpfiles=$tmpfiles" $tmpd"
132 # fi
133 # fi
134 control=$3
135 td=`getcachedir "article/$2"`
136 [ -d "$td" ] || mkdir -p $td
137 tbl=${1%%[!A-Z0-9a-z_]*} rowid=${2%%[!A-Z0-9a-z_]*}
138 err blog_showentry: rowid=$rowid, '$2'=$2 user=$user
139 ts=${tbl}_s tm=${tbl}_m
140 at=article as=article_s am=article_m
141 serial=$(($(date +%s)-1420038000))s$$
142 cannotread='<div class="relative"><img class="overlap" src="img/key.png" alt="(読み取り不可)"></div>'
143 blog_writable $rowid $user
144 rc=$?
145 if [ $rc = 0 ]; then
146 iswritable=true
147 ismem=true
148 else
149 iswritable=false
150 if [ $((rc & $BLOG_NOTMEM)) -gt 0 ]; then
151 ismem=false
152 else
153 ismem=true
154 fi
155 fi
156 # This function grasps blog entry definiton directly.
157 # blog: id
158 # blog_s: title,ctime,heading
159 # blog_m: *article
161 blogowner=`getvalbyid blog owner "$2"`
162 isgroup "$blogowner" && isgroup=true || isgroup=false
163 isgrpadmin=false
164 isgrpowner "$user" "$blogowner" && isgrpadmin=true
166 # 2015-10-05 check readable
167 if ! $iswritable; then
168 # err blogowner=$blogowner
169 if $isgroup; then
170 regmode=`getgroupattr "$blogowner" regmode`
171 # err regmode=$regmode
172 if [ x"$regmode" = x"moderated" ]; then
173 # if ! ismember $user $blogowner; then
174 if ! $ismem; then
175 echo "加入してからどうぞ" | html p
176 return
177 fi
178 fi
179 fi
180 fi
181 blog_notify=`getvalbyid blog notify "$rowid"`
182 blog_team=`blog_getteam "$rowid"`
183 blog_mode=`getvalbyid blog mode "$rowid"`
184 case "$blog_notify" in # "all", "admin" or "no" (or NULL)
185 admin) notifyto=adm ;;
186 *) notifyto="" ;;
187 esac
188 case $blog_mode in
189 *quiz*|*close*|"") # When blog_mode is "", fallback to quiz/close
190 f_exclusive=1
191 if $isgroup; then
192 qgrp=`sqlquote "$blogowner"`
193 if $isgrpadmin; then
194 F_UNREADABLE="''"
195 else
196 if [ x"$blog_mode" = x"quiz" ]; then
197 F_UNREADABLE="CASE
198 WHEN author IN (SELECT user FROM grp_adm WHERE gname=$qgrp)
199 THEN ''
200 WHEN author = '$user'
201 THEN ''
202 ELSE 'Unreadable'
203 END"
204 else
205 F_UNREADABLE="'Unreadable'"
206 fi
207 fi
208 else # User blog
209 if [ x"$blog_mode" = x"quiz" ]; then
210 F_UNREADABLE="CASE
211 WHEN author = '$blogowner'
212 THEN '' ELSE 'Unreadable'
213 END"
214 else
215 F_UNREADABLE="'Unreadable'"
216 fi
217 fi
218 ;;
219 *) f_exclusive=''
220 F_UNREADABLE="''"
221 ;;
222 esac
224 # err "SELECT id from $tbl where rowid=$rowid"
225 id=`query "select id from $tbl where rowid=$rowid;"`
226 #err id=$id
227 #err "select val from $ts where key='title' and id='$id';"
229 #(1)Display root article
230 cat<<EOF
231 <form class="replyblog" action="$myname?replyblog+${rowid}#bottom" method="POST" enctype="multipart/form-data">
232 <table class="bloghead">
233 EOF
235 href="<a href=\"?editheading+$rowid\" accesskey=\"e\" title=\"Shortcut: E${nl}Edit\"> 編集 </a>"
236 if $ismem; then
237 case $blog_mode in
238 *report*|*quiz*|*enquete*)
239 href2="<a href=\"?lshandout+$rowid\" accesskey=\"l\" title=\"Shortcut: L${nl}List Handouts\"> 提出状況 </a>"
240 case "$isgrpadmin$blog_mode" in
241 false*closed*|false*quiz|false*enquete*) ;;
242 *)
243 href3="(ファイル取得[<a href=\"?gethandout+$rowid\" accesskey=\"f\" title=\"Shortcut: F${nl}File Retrieval\">記事順</a>|<a href=\"?gethandout+$rowid+by_uname\" accesskey=\"u\" title=\"Shortcut: F${nl}File Retrieval by User\">著者順</a>])"
244 ;;
245 esac
246 ;;
247 esac
248 fi
249 href4="<a href=\"#bottom\" accesskey=\"b\" title=\"Shortcut: B${nl}to the Bottom\"> 末尾へ</a>"
250 $isgrpadmin &&
251 href5="<a href=\"?blogseen+$rowid\" accesskey=\"s\" title=\"Shortcut: S${nl}State of Accesses\"> 読刻</a>"
252 quizmodefile=$tmpd/quiz; rm -f "$quizmodefile" # XXX: Global state
253 midfile=$tmpd/midfile
255 query<<-EOF > $midfile
256 SELECT coalesce((SELECT "yes" FROM blog
257 -- GrpAdmin CAN EDIT heading since 2019-08-15
258 WHERE '$isgrpadmin' = 'true'
259 OR (rowid=$rowid AND author='$user')),
260 ''),
261 max(CASE key WHEN 'ctime' THEN val END) ctime,
262 max(CASE key WHEN 'heading' THEN hex(val) END) heading,
263 CASE (SELECT val FROM $ts WHERE key="mode" AND id="$id")
264 WHEN 'report-closed' THEN 'レポート提出用(closed)'
265 WHEN 'report-open' THEN 'レポート提出用(open)'
266 WHEN 'quiz' THEN 'クイズ'
267 WHEN 'enquete' THEN '集計'
268 ELSE ''
269 END
270 FROM $ts WHERE id='$id' GROUP BY id;
271 EOF
272 if test -s $midfile && IFS='|' read edit ctime hexhead blogtype < $midfile
273 then
274 cat<<-EOF
275 <tr><td>${edit:+$href }$ctime $blogtype $href2${edit:+$href3} $href4 $href5</td></tr>
276 <tr class="preface${frozen_class:+ }$frozen_class">
277 <td>`echo "$hexhead"|unhexize|htmlescape|hreflink|minitbl`</td></tr>
278 </table>
279 EOF
280 case "$blogtype" in
281 "クイズ"|"XXXX集計")
282 echo "${blogtype}モードは本人と管理者の書き込みのみが表示されます。"
283 ;;
284 esac | html p 'class="warn"'
286 if [ x"$blogtype" = x"クイズ" -o x"$blogtype" = x"XXXX集計" ]; then
287 if $isgroup; then
288 # Failsafe to query timeout
289 qgrp=`sqlquote "$blogowner"`
290 cat<<-EOF > $quizmodefile
291 AND (author IN (SELECT user FROM grp_adm WHERE gname=$qgrp)
292 OR
293 author='$user')
294 EOF
295 if $isgrpadmin; then #
296 : > $quizmodefile
297 fi
298 else # if user-blog
299 if [ x"$user" != x"$blogowner" ]; then
300 cat<<-EOF > $quizmodefile
301 AND author IN ('$blogowner', '$user')
302 EOF
303 fi
304 fi
305 fi
306 else # Cannot read SQL output
307 echo "時間をおいて繋いでください(Please visit later)." | html p
308 return
309 fi
310 ## Parse control sequence
311 nlimit=$listartlimit
312 case "$control" in
313 n:[Aa][Ll][Ll])
314 unset nlimit ;;
315 n:*)
316 nlimit=${control##*:}
317 nlimit=${nlimit%%[!A-Z0-9a-z_]*}
318 ;;
319 esac
320 lkhome="<a href=\"$myname?home" lke='">'
321 lkedit="<a href=\"$myname?editart"
322 hlink="$myname?home" elink="$myname?editart"
323 catlink="$myname?showattc+article_m"
324 deficon="img/file-icon.png"
325 # 2016-08-15 Newer flag introduced
326 atime=`query "SELECT time FROM acclog
327 WHERE tbl='blog' AND tblrowid=$rowid AND user='$user';"`
328 iconcleaner=$tmpd/iconcleaner.$$
329 [ -s $quizmodefile ] && cond_qz=`cat $quizmodefile`
330 # *** DO NOT USE query(), use "sq $db" instead here ***
331 # because the next block in pipe line uses query() repeatedly.
332 ###### TEST: 2020-04-23 Use intermediate file to shorten duration of db-lock
333 ###### sq $db<<EOF |
334 query <<EOF > $midfile
335 WITH a_s AS (
336 SELECT id,
337 max(CASE key WHEN 'ctime' THEN val END) TIME,
338 max(CASE key WHEN 'text' THEN val END) TEXT
339 FROM article_s
340 GROUP by id
341 )
342 SELECT a.id,
343 CASE author
344 WHEN '$user' THEN a.rowid||'+'||$rowid
345 ELSE ''
346 END edit,
347 CASE -- 「通知送信」ボタンの有無
348 WHEN '$notifyto' = '' THEN '' -- 不要モードならなし
349 WHEN '$user' = author THEN '' -- 筆者自身ならなし
350 ELSE "yes"
351 END notify,
352 (SELECT rowid FROM user WHERE name=author) user_rid,
353 author,
354 coalesce((SELECT val FROM user_s
355 WHERE name=author AND key='gecos'),
356 author) uname,
357 (SELECT val FROM user_s WHERE name=author AND key='$iconcachekey')
358 icon,
359 a.rowid,
360 s.TIME,
361 CASE WHEN s.TIME < '2019-05'
362 THEN printf('平成%d年%d月%d日%s',
363 substr(s.TIME, 1, 4)-1988,
364 substr(s.TIME, 6, 2),
365 substr(s.TIME, 9, 2),
366 substr(s.TIME, 12)
367 )
368 WHEN s.TIME < '2020'
369 THEN printf('令和元年%d月%d日%s',
370 substr(s.TIME, 6, 2),
371 substr(s.TIME, 9, 2),
372 substr(s.TIME, 12))
373 WHEN s.TIME < '2050'
374 THEN printf('令和%d年%d月%d日%s',
375 substr(s.TIME, 1, 4)-2018,
376 substr(s.TIME, 6, 2),
377 substr(s.TIME, 9, 2),
378 substr(s.TIME, 12))
379 ELSE s.TIME
380 END reki,
381 CASE WHEN s.TIME > '$atime' THEN 'new' ELSE '' END newer,
382 hex(s.TEXT),
384 $F_UNREADABLE cannotread,
386 (SELECT group_concat(rowid||':'||length(bin)||':'||hex(val), ' ')
387 FROM article_m
388 WHERE id=a.id AND key='image') imxgids
389 FROM (select rowid,id,author from article
390 where blogid in
391 (select id from blog where rowid=$rowid)
392 $cond_qz) a
393 LEFT JOIN
394 a_s s
395 ON a.id=s.id;
396 EOF
397 if [ $? -ne 0 -a ! -s $midfile ]; then
398 echo "時間をおいてください(Visit later please)." | html p
399 return
400 fi
401 echo '<table class="blog_replies"> <!-- blog:blog_showentry() main table -->'
402 # If, nLimit = 50
403 # show article:1, hide(2, 3), show(4, ...)
404 # Therefore hide 2 or more article when narts>53
405 narts=`wc -l < $midfile`
406 if [ -n "$nlimit" -a "$narts" -gt "$((nlimit+2))" ]; then
407 newtop=`cat -n $midfile | grep "|new|" | head -1 | cut -f1`
408 if [ -n "$newtop" ]; then
409 afternew=$((narts-newtop+1))
410 [ $afternew -gt $((nlimit+2)) ] && nlimit=$((afternew+0))
411 err Newtop=$newtop lines=$narts afternew=$afternew nlim=$nilmit
412 fi
413 fi
414 if [ $nlimit -lt $((narts-2)) ]; then
415 n=0
416 omitline=$td/omitline
417 #CAT="tail -n $nlimit"
418 CAT=cat
419 limitedmsg="<span class=\"warn\">※最新${nlimit}件のみの表示※</span>"
420 showalllink="<a title=\"Show All\" href=\"?replyblog+$rowid+n:all\">全件表示</a>"
421 cat<<-EOF > $omitline
422 <tr class="warn">
423 <th>:<br>$limitedmsg<br>($((narts-$nlimit-1))件省略)<br>:</th>
424 <th>$showalllink</td></th>
425 EOF
426 else
427 CAT=cat
428 fi
429 # Start blog_replies table
430 $CAT $midfile |
431 while IFS='|' read id edit notify uid author uname icon aid \
432 tm reki new hte fa imgids
433 do
434 if [ -n "$omitline" ]; then
435 n=$((n+1))
436 if [ $n -eq 1 ]; then
437 :
438 elif [ $n -eq 2 ]; then
439 cat $omitline
440 continue
441 elif [ $n -lt $((narts-nlimit+1)) ]; then
442 continue
443 fi
444 fi
445 mf2=$tmpd/midfile2
446 cachefile="$td/$id.row.html"
447 stampfile="$td/$id.row.stamp"
448 editlink="${edit:+<a href="$elink+$edit">編集</a> }"
449 nt="<label style=\"font-size: 70%;\"><input type=\"checkbox\"\
450 name=\"notifyto\" value=\"$uid\">返信通知送信</label>"
451 # fa is file accessibility flag # err "----r=$aid fa=[$fa]----"
453 # First, check the availability of user-icon.
454 # If not existent, clear and reset row cache by rm $stampfile
455 if [ ! -s "$icon" ]; then
456 rm -f "$stampfile"; unset stampfile
457 fi
458 if test -s "$stampfile" &&
459 test -s "$cachefile" &&
460 { ts=`cat "$stampfile"`; test -n "$ts"; } &&
461 /bin/test "$ts" '>' "$tm" && # Cache timestamp is newer
462 test "$stampfile" -nt "$icon"; then # UserIcon is older
463 : Nothing to do
464 else
465 { ######## New ROW creation begins here ######## >$cachefile
466 cachestamp=$tmpd/cache.$$.stamp
467 touch $cachestamp
468 tdcls="__NEWCLS__repatt"
469 if [ -s "$icon" ]; then
470 icfn=`echo "$icon"|htmlescape`
471 picon="<p class=\"proficon\"><a href=\"$hlink+$uid\" title=\"${author%@*}\"><img src=\"$icfn\"></a></p>"
472 else
473 echo "DELETE FROM user_s WHERE key='$iconcachekey' AND
474 val=`sqlquotestr \"$icon\"`;" >> $iconcleaner
475 picon=""
476 fi
478 cat<<EOF
479 <tr id="$id">
480 <td class="$tdcls">${picon}__EDIT__<a href="#$aid">#$aid</a>
481 <a href="$hlink+$uid" title="${author%@*}">`echo $uname|htmlescape`</a>
482 <span title="$tm">${reki:-$tm}</span>
483 <__NOTIFY__></td>
484 EOF
485 echo -n "<td id=\"$aid\" class=\"repl\">"
486 echo "$hte"|unhexize|htmlescape|hreflink|minitbl
487 usecache='' tsfile=$td/$id.stamp
488 for i in $imgids; do
489 mrid=${i%%:*}; i=${i#*:}; sz=`size_h ${i%%:*}`
490 fn=`echo "${i#*:}"|unhexize`
491 fnb=$fn"(${sz})"
492 case "$fn" in
493 *.[Pp][Nn][Gg]|*.[Jj][Pp][Gg]|*.[Jj][Pp][Ee][Gg]|*.[GgTt][Ii][Ff])
494 # fmt=${fn##*.} # convert - jpg:- is slow...why
495 case "$fn" in
496 *.[Pp][Nn][Gg]) fmt=png ;;
497 *.[Gg][Ii][Ff]) fmt=gif ;;
498 *) fmt=jpeg ;;
499 esac
500 outfile=$td/$mrid-${fn%.*}.$fmt
501 #err fn=$fn outfile=$outfile
502 #err "usecache=$usecache `ls -l $outfile`"
503 #err tm=$tm
504 #err tsfile=$tsfile=`cat $tsfile`
505 if [ -s "$outfile" ] && # $outfile should be > 0
506 { [ "$usecache" ] || # And usecache flag is true, or...
507 { [ -s "$tsfile" ] && [ x"`cat $tsfile`" = x"$tm" ]
508 };}; then
509 usecache=1 # Set usecache flag on
510 cat<<-EOF
511 <a href="$catlink+$mrid"><img src="$outfile">
512 $fnb</a>
513 EOF
514 # !!NOTE!! Create row stamp ONLY WHEN imgcache is active
515 else
516 query "SELECT hex(bin) FROM article_m WHERE rowid=$mrid;" \
517 > $mf2 # Stop query here 2020-04-23
518 if cat $mf2 | unhexize \
519 | convert -define ${fmt}:size=100x100 -resize 100x100'>' \
520 - ${fmt}:- > $outfile
521 then
522 cat "$outfile" \
523 | hexize \
524 | sed -e 's/\(..\)/%\1/g' \
525 -e "s|^|<a href=\"$catlink+$mrid\"><img src=\"data:image/$fmt,|" \
526 -e "s|\$|\">$fnb</a>|"
527 unset stampfile # img data stream is not suitable to cache
528 echo $tm > $tsfile
529 else # Failed to convert
530 rm -f $outfile
531 echo "<a href=\"$catlink+$mrid\">$fnb</a>"
532 fi
533 fi
534 ;;
535 *)
536 echo "<__UNREADABLE__><a href=\"$catlink+$mrid\"><img src=\"$deficon\">$fnb</a>"
537 ;;
538 esac
539 done
540 echo "</td></tr>"
541 } > "$cachefile.$$" ######## New ROW Creation Ends here ########
542 # Care about race condition
543 if [ -z "$hte" -a -s $cachefile -a $cachefile -nt $cachestamp ]; then
544 # If other process have created cache, give up to serve our file
545 rm -f $cachefile.$$
546 else
547 mv -f $cachefile.$$ $cachefile
548 fi
549 test -n "$stampfile" && date "+%F %T" > $stampfile
550 fi
551 # Printing a cached row
552 sed -e "/^<td class=/s/__NEWCLS__/$new${new:+ }/" \
553 -e "/^<td class=/s,__EDIT__,$editlink," \
554 -e "/^<__NOTIFY__>/s,,${notify:+$nt}," \
555 -e "/<__UNREADABLE__>/s,,${fa:+$cannotread}," \
556 $cachefile
557 done
559 help="=== コメントに使用できる特殊記法(記号は全て半角) ===
560 行頭に href=URL でURLへのリンク
561 行頭に iframe=URL でURL先を開く iframe
562 行頭「* 」で箇条書、次の行頭空白で継続、行頭詰めると箇条書終わり
563 行頭「1. 」で番号付、2行目以降も「1. 」で勝手に番号増える、行頭詰めで終わり
564 [[#記事番号]] でs4内の記事番号に飛ぶリンク
565 [[#検索キーワード]] でs4内の記事検索(記号はいくつか使えない)
566 [[URL]] でURLへのリンク、 [[URL|文字列]]でアンカー文字列指定
567 {{画像URL}} でインライン画像、 {{画像URL|幅}} でピクセル幅指定
568 {{{URL}}} でURL先を開く iframe、 {{{URL|高さ}}} ピクセル高さ指定
569 行頭: ## 大見出し, ### 中見出し, #### 小見出し
570 行末の2連続スペースで強制改行(<br>)
571 |*見出し列|列2|列3… と行頭から始まる縦棒区切り行を続けて表
572 ' *語群* ' で強調(両側の空白必要、** でもっと強調。*の代わりに _ でも可)
573 - [ ] と - [x] でチェックボックス"
574 touchhelp="${touchpanel:+<p class=\"help\">$help</p>}"
575 filehelp="《添付の注意》
576 $file_accept_help"
577 ntmode="通知モード=$blog_notify${blog_team:+ (team=$blog_team)}
578 記事の1行目を「## 」(半角シャープシャープ空白=大見出し)
579 にするとそれがSubject(件名)になります。
580 If the first line begins with &quot;## &quot;, sent it as Subject of email."
581 textform=$(cat<<-EOF
582 <div class="fold">
583 <input type="checkbox" id="cmt" checked><label
584 accesskey="c" title="C" for="cmt">コメントする</label><div>
585 <table class="b">
586 <tr><td><textarea id="text" name="text" cols="72" rows="4" title="$help">
587 </textarea>$touchhelp</td></tr>
588 <tr><td>添付ファイル(${filesize_max_MB}以下):
589 `cgi_file image "" "$file_accept title=\"$filehelp\" multiple"`
590 </td></tr>
591 </table>
592 <input type="submit" value="送信" class="$blog_notify" title="$ntmode">
593 <input type="reset" value="リセット"></div></div>
594 EOF
595 )
596 cat<<-EOF
597 </table> <!-- end of s4-blog:blog_showentry() main table -->
598 <p class="update_link"><a
599 href="?reload/$rowid" accesskey="r"
600 title="Shortcut: R${nl}Reload">再読込</a> / <a
601 href="#title" id="bottom" accesskey="t"
602 title="Shortcut: T${nl}to the Top">先頭へ</a>
603 ${showalllink:+/ `echo $showalllink|sed 's/n:all/&\#bottom/'`$limitedmsg}</p>
604 EOF
605 $iswritable && cat<<-EOF
606 <div class="blogcomment">
607 <input type="hidden" name="blogid" value="$id">
608 <input type="hidden" name="id" value="`genserial`">
609 <input type="hidden" name="stage" value="replyblog">
610 $textform
611 </div>
612 </form> <!-- End of s4-blog:blog_showentry() main form -->
613 EOF
614 # Clean up orphaned icon cache
615 [ -s $iconcleaner ] && query ".read '$iconcleaner'"
616 # Record access log
617 acclog blog $rowid
618 }
620 lshandout() {
621 # $1=rowid of blog (numericalized in s4.cgi)
622 blog_writable $1 $user
623 rc=$? # =0: writable, $BLOG_NOTMEM bit set => not member
624 if [ $((rc & $BLOG_NOTMEM)) -gt 0 ] ; then
625 echo "メンバー以外は利用できません。" | html p; return
626 fi
627 time=`getvalbyid blog ctime $1|colrm 11`
628 owner=`getvalbyid blog owner $1`
629 title=`getvalbyid blog title $1`
630 ge=`gecos "$owner"`
631 htmlowner=`echo ${ge:-$owner}|htmlescape`
632 fh=$tmpd/formhead
633 echo "$time [$title]@$htmlowner" > $fh
634 lshandoutsub "$owner" "$@" \
635 |_m4 -D_TITLE_="提出状況" \
636 -D_H1_="提出状況" \
637 -D_FORMHEAD_="syscmd(cat $fh)" \
638 -D_FORM_="syscmd(cat)" -D_DUMPHEAD_= -D_DUMPTABLE_= \
639 $layout/html.m4.html $layout/form+dump-whead.m4.html
640 gn=`echo $owner|htmlescape`
641 echo "<p><a href=\"?lshandoutall+$1\">グループ $gn すべてのレポート板集計</a></p>"
642 }
643 gethandoutcsv() {
644 # contenttype; echo
645 CATCSV=1 lshandoutall "$1"
646 }
647 gethandoutcsv2() {
648 # contenttype; echo
649 SQL=$(cat<<-EOF
650 WITH this_blog_articles AS (
651 SELECT rtb.id bid, rtb.brid, a.id aid, author, title, ctime
652 FROM report_type_blogs rtb JOIN article a ON rtb.id=a.blogid
653 ), text_or_file AS (
654 SELECT bid, author, title, ctime, 'text' shu, count(val) cnt
655 FROM this_blog_articles tba, article_s s
656 ON tba.aid=s.id
657 WHERE key='text'
658 GROUP by bid, author
659 UNION
660 SELECT bid, author, title, ctime, 'file' shu, count(val) cnt
661 FROM this_blog_articles tba, article_m m
662 ON tba.aid=m.id
663 WHERE key='image'
664 GROUP by bid, author
665 ), count_list AS (
666 SELECT author,
667 substr(ctime, 1, 10)||upper(substr(shu, 1, 1)) unit,
668 cnt
669 FROM text_or_file
670 )
671 SELECT gecos "名前",
672 substr(author, 1, instr(author, '@')-1) "uname",
673 unit,
674 cnt "post"
675 FROM count_list cl JOIN gecoses g ON cl.author=g.name;
676 EOF
677 ) gethandoutcsv "$1"
678 }
679 lshandout_ulink_table() {
680 # NO Args. Read stdin as SQL
681 echo '<table class="b td3rr td3evw">'
682 hrb="<a href=\"?home+"
683 # echo "$sql" | sq -header -html $db \ # Formerly, this is called via sq()
685 printf ".mode html\n.header ON\n" | query
686 cat | query \
687 | sed -e "s,\(<TR><TD>\)\([^ ]*\) \(.*\)</TD>,\1$hrb\2\">\3</TD>," -e 's,<TD>0</TD>,<TD class="warn">0</TD>,'
688 echo '</table>'
689 printf ".mode list\n.header OFF\n" | query
690 }
691 lshandoutall() {
692 # $1=rowid of blog
693 blog_writable $1 $user
694 rc=$? # =0: writable, $BLOG_NOTMEM bit set => not member
695 if [ $((rc & $BLOG_NOTMEM)) -gt 0 ] ; then
696 echo "メンバー以外は利用できません。" | html p; return
697 fi
698 rowid=$(($1 + 0))
699 owner=`getvalbyid blog owner $1`
700 qowner=`sqlquotestr "$owner"`
702 query<<-EOF
703 CREATE TEMPORARY TABLE IF NOT EXISTS report_type_blogs AS
704 WITH blog_owner_mode AS (
705 SELECT id,
706 blog.rowid brid,
707 max(CASE key WHEN 'owner' THEN val END) owner,
708 max(CASE key WHEN 'mode' THEN val END) mode,
709 max(CASE key WHEN 'title' THEN val END) title,
710 max(CASE key WHEN 'ctime' THEN val END) ctime
711 FROM blog NATURAL JOIN blog_s
712 GROUP BY id
713 )
714 SELECT id, brid, title, ctime FROM blog_owner_mode
715 /* WHERE owner=$qowner AND mode LIKE '%report%'; */
716 WHERE owner=$qowner
717 AND
718 (mode LIKE '%report%' OR mode LIKE '%quiz%'
719 OR mode LIKE '%enquete%');
720 /* ↑これでレポート形式の blogid 一覧を得る */
721 EOF
722 if [ -z "$CATCSV" ]; then
723 _m4 -D_TITLE_="提出状況" $layout/html.m4.html
724 ge=`gecos "$owner"`
725 tbls=""
726 grptxt=`echo "${ge:-$owner}"|htmlescape`
727 echo "<h1>$grptxt 書き込み状況一覧</h1>"
728 fi
729 if [ -z "$SQL" ]; then
730 bridlist=`query "SELECT brid FROM report_type_blogs;"`
731 for brid in $bridlist; do # Skip this loop if $SQL set
732 brid=$(($brid + 0)) # Ensure to be a number
733 [ $brid = 0 ] && continue
734 time=`getvalbyid blog ctime $brid|colrm 11`
735 title=`getvalbyid blog title $brid|htmlescape`
736 state=`getvalbyid blog state $brid|htmlescape`
737 tt="handout_$brid"
738 [ "$state" = "frozen" ] && frozen=" $FROZEN_TAG" || frozen=""
739 if [ -z "$CATCSV" ]; then
740 echo "<h2>$time - <a href=\"?replyblog+$brid\">$title</a>$frozen</h2>"
741 lshandoutsub "$owner" $brid "$tt"
742 else
743 lshandoutsub "$owner" $brid "$tt" >/dev/null # Only create temp.table
744 fi
745 tbls="$tbls${tbls:+ NATURAL JOIN }$tt"
746 done
747 fi
748 sql=${SQL:-"SELECT * FROM $tbls;"}
749 if [ -z "$CATCSV" ]; then
750 echo "<hr><h2>総合</h2>"
751 echo "$sql" | lshandout_ulink_table
752 echo "<h2>総合(<a href=\"?gethandoutcsv+$rowid\">CSV</a>)</h2>"
753 printf ".mode csv\n.header ON\n" | query
754 echo '<pre class="list">'
755 echo "$sql" | query | sed 's/^"[0-9]* /"/'
756 echo "</pre>"
757 echo "<pre><a href=\"?gethandoutcsv2+$rowid\">縦持ちCSV</a></pre>"
758 else
759 contenttype "Application/CSV"
760 printf ".mode csv\n.header ON\n" | query >/dev/null
761 fn=report-count.csv
762 printf 'Content-Disposition: filename="%s"\n' "$fn"
763 outfile=$tmpd/out-$$.csv
764 echo "$sql" | query | sed 's/^"[0-9]* /"/' > $outfile
765 echo "Content-Length: " `cat $outfile | wc -c`; echo
767 cat $outfile
768 exit 0
769 fi
770 printf ".mode list\n.header OFF\n.separator |\n" | query
771 }
772 lshandoutsub() {
773 # $1=owner $2=rowid of blog &optional $3=temp_table name
774 qgname=`sqlquote "$1"`
775 if isgroup "$1"; then
776 sample="(select user from grp_mem where gname=$qgname)"
777 else
778 sample="(select distinct author as user from arts)"
779 echo "(集計は板への投稿者のみ)" | html p
780 fi
781 tmpname="${3:-handout_$2}"
782 sql="CREATE TEMPORARY TABLE IF NOT EXISTS $tmpname AS
783 with arts as (select id,author from article \
784 where blogid=(select id from blog where rowid=$2))\
785 select (select rowid from user where name=c0.user)||' '|| \
786 (select gecos from gecoses where name=c0.user) as 'メンバー',\
787 substr(c0.user, 1, instr(c0.user, '@')-1) 'uname',\
788 sum(case when c1.key is not null then 1 else 0 end)\
789 as '[$title] コメント記入',\
790 sum(case when c2.key is not null then 1 else 0 end)\
791 as '[$title] ファイルの提出'\
792 from $sample c0 \
793 left join (select id,author from arts) a\
794 on c0.user=a.author\
795 left join (select id,key from article_s where key='text') c1\
796 on a.id=c1.id left join (select id,key from article_m ) c2\
797 on c1.id=c2.id group by c0.user order by c0.user;\
798 \
799 SELECT * FROM $tmpname;"
800 # err ishandoutsub: sql="$sql"
801 echo "$sql" | lshandout_ulink_table
802 }
803 gethandout() {
804 # $1=rowid of blog
805 rid=`numericalize "$1"`
806 test x"$2" = x"by_uname" && by_uname="$2"
807 blog_writable $rid $user
808 rc=$? # =0: writable, $BLOG_NOTMEM bit set => not member
809 if [ $((rc & $BLOG_NOTMEM)) -gt 0 ] ; then
810 contenttype; echo
811 echo "メンバー以外は利用できません。" | html p; return
812 fi
813 # Here, this blog is writable by $user
814 mode=`getvalbyid blog mode $1`
815 owner=`getvalbyid blog owner $1`
816 blogauthor=`getvalbyid blog author $1`
817 isopenblogauthor=false
818 if [ x"$user" = x"$owner" ]; then
819 : OK
820 elif isgrpowner "$user" "$owner"; then
821 : OK
822 elif [ x"$blogauthor" = x"$user" ]; then
823 # Non-admin Author of blog cannot do gethandout() in report-closed mode
824 # for avoidance the risk of fake report-closed blog.
825 case "$mode" in # Only report-open can be handled by blog author
826 *open*) isopenblogauthor=true ;;
827 esac
828 else
829 contenttype; echo
830 echo "グループ管理者のみ取得できます。" | html p; return
831 fi
832 copy2csv=false
833 blogid=`getvalbyid blog id $1`
834 isgroup "$owner" && isgroup=true || isgroup=false
835 isgrpowner "$user" "$owner" && isgrpadmin=true || isgrpadmin=false
837 i=0
838 midfile=$tmpd/midfile
839 bd=$tmpd/archive.$$
840 mkdir $bd
841 case "$mode" in
842 *quiz*)
843 copy2csv=true ;;
844 *enquete*)
845 copy2csv=true
846 csvline=`getvalbyid blog heading $1 | grep "..*,." | head -1`
847 # Create CSV-base table for questionnaire
848 # If heading in blog_s has at least 1 CSV line,
849 # we take the line as column list.
850 # Otherwise we produce two column CSV as below:
851 # USER,ANSWER
852 query "DROP TABLE IF EXISTS tmp_q;"
853 if [ -n "$csvline" ]; then
854 query <<-EOF
855 CREATE TEMPORARY TABLE tmp_q("user", $csvline);
856 EOF
857 if [ $? != 0 ]; then
858 contenttype; echo
859 cat <<-EOF | html p; exit
860 掲示板のヘッダにあるCSV定義が不正でCSV出力できません。
861 $csvline
862 空白なしの項目名を半角カンマ区切りで1行で書いてください。
863 EOF
864 fi
865 else
866 query <<-EOF
867 CREATE TEMPORARY TABLE tmp_q(user text PRIMARY KEY, answer);
868 EOF
869 fi
870 esac
871 if $copy2csv; then
872 mkdir $bd/$rid
873 outcsv=$bd/$rid/migrate-$rid.csv
874 fullcsv=$bd/$rid/all-text-full-$rid.csv
875 sq "$db" <<-EOF | tr '|' ',' > $outcsv
876 SELECT author as "USER",
877 replace(val, x'0a', ',') as "${csvline:-ANSWER}"
878 FROM article a JOIN article_s s ON a.id=s.id
879 AND blogid=(SELECT id FROM blog WHERE rowid=$rid)
880 AND s.key='text';
881 EOF
882 sq "$db" <<-EOF > $fullcsv
883 .mode csv
884 .head 1
885 SELECT author as "ユーザ",
886 (SELECT gecos FROM gecoses g WHERE author=g.name) as "表示名",
887 val as "テキスト"
888 FROM article a JOIN article_s s ON a.id=s.id
889 AND blogid=(SELECT id FROM blog WHERE rowid=$rid)
890 AND s.key='text';
891 EOF
892 fi
893 query <<-EOF > $midfile # Using tempfile for quick db-unlock
894 SELECT a.rowid, a.id artid, a.author, hex(s.val)
895 FROM article a JOIN article_s s ON a.id=s.id
896 WHERE blogid=(SELECT id FROM blog WHERE rowid=$rid)
897 ORDER BY a.rowid;
898 EOF
899 cat $midfile | while IFS='|' read rowid artid author text; do
900 $isgrpowner || $isopenblogauthor \
901 || isfilereadable $user article_s $rowid || continue
902 if [ "$by_uname" ]; then
903 dir=`printf $bd/%d/%s "$rid" "$author"`
904 else
905 dir=`printf $bd/%d/%06d "$rid" "$rowid"`
906 fi
907 txt=`printf %06d $rowid`.txt
908 test -d "$dir" || mkdir -p "$dir"
909 echo "$author" > "$dir"/Author.txt
910 echo "$text" | unhexize > "$dir/$txt"
911 i=0
912 query "SELECT m.rowid, m.val FROM article_m m \
913 WHERE id='$artid' AND m.key IN ('image', 'document', 'binary');" \
914 | while IFS='|' read mrowid filename; do
915 i=$((i+1))
916 if [ "$by_uname" ]; then
917 outfile=`printf "%s/%06d-%s" "$dir" $rowid "$filename"`
918 else
919 outfile=`printf "%s/%02d-%s" "$dir" $i "$filename"`
920 fi
921 query "SELECT quote(bin) FROM article_m WHERE rowid=$mrowid;" \
922 | unhexize > "$outfile"
923 done
924 done
925 if [ ! -d $bd/$rid ]; then
926 contenttype; echo
927 echo "取得できるファイルがありませんでした。" | html p
928 return
929 fi
931 if $copy2csv; then
932 query <<-EOF > $bd/$rid/all-text-1stline-$rid.csv
933 .mode csv
934 .head 1
935 CREATE TEMPORARY TABLE IF NOT EXISTS tmp_q("user", "TEXT");
936 .import $outcsv tmp_q
937 SELECT * FROM tmp_q;
938 .mode list
939 .head 0
940 EOF
941 fi
942 err "BDLIST: `ls -l $bd`"
943 arcname=archive-$rid.tar.gz
944 ### outstdout=true
945 (cd $bd
946 # query() CANNOT BE used in this subshell
947 if [ "$outstdout" ]; then
948 cat <<-EOF
949 Content-type: application/x-gzip
950 Content-Disposition: filename="$arcname"
952 EOF
953 tar zcf - $rid
954 return
955 else
956 tar zcf .archive.tar.gz $rid && mv .archive.tar.gz "$arcname"
957 err Creating tar archive "`ls -l "$arcname"`"
958 fi
959 )
960 arcfile=$bd/$arcname
961 echo "Content-type: application/x-gzip"
962 echo "Content-Length: `cat $arcfile|wc -c`"
963 echo "Content-Disposition: filename=\"$arcname\""
964 echo
965 cat $arcfile
966 }
967 blogseen() { # $1 = blogid
968 blogid=${1%%[!0-9]*}
969 if [ -z "$blogid" ]; then
970 echo "Invalid blog id" | html p; exit
971 fi
972 blog_writable "$blogid" "$user"
973 rc=$? # =0: writable, $BLOG_NOTMEM bit set => not member
974 if [ $((rc & $BLOG_NOTMEM)) -gt 0 ] ; then
975 echo "メンバー以外は利用できません。" | html p; return
976 fi
977 owner=`getvalbyid blog owner $rowid`
978 qowner=`sqlquotestr "$owner"`
979 grprowid=`query "SELECT rowid FROM grp WHERE gname=$qowner;"`
980 ge=`gecos "$owner" | htmlescape`
981 title=`getvalbyid blog title $rowid | htmlescape`
982 h1="アクセス時刻"
983 link2board="<a href=\"?replyblog+$rowid\">$title</a>"
984 link2group="<a href=\"?grp+$grprowid\">$ge</a>"
985 _m4 -D_TITLE_="$h1" $layout/html.m4.html
986 echo "$h1" | html h1
987 echo "[$link2board]@$link2group" | html h2
988 warn=' class="warn"'
989 cat <<-EOF
990 <table class="b">
991 <tr><th>メンバー</th><th>uname</th><th>最終閲覧時刻</th></tr>
992 EOF
993 query <<-EOF |
994 WITH grpmem as (
995 SELECT user, (SELECT gecos FROM gecoses WHERE name=user) gecos
996 FROM grp_mem
997 WHERE gname=(SELECT val FROM blog_s
998 WHERE id=(select id from blog where rowid=$blogid)
999 AND key='owner')
1000 ), acctime AS (
1001 SELECT user, max(time) atime
1002 FROM tblaccesses
1003 WHERE tbl='blog' AND tblrowid=$blogid
1004 GROUP BY user
1006 SELECT g.user,
1007 (SELECT rowid FROM user u WHERE u.name=g.user),
1008 hex(gecos),
1009 atime
1010 FROM grpmem g LEFT JOIN acctime t
1011 ON g.user = t.user
1012 GROUP BY g.user
1013 ORDER BY atime DESC;
1014 EOF
1015 while IFS='|' read u uid hexge time; do
1016 td=${time:+"<td>"} # If the variable time is set, td=<td>
1017 td=${td:-"<td$warn>"} # else td=<td class="warn">
1018 cat <<-EOF
1019 <tr>
1020 <td><a href="?home+$uid">`echo "$hexge"|unhexize|htmlescape`</a></td>
1021 <td>`echo ${u%%@*}|htmlescape`</td>
1022 $td${time:----}</td></tr>
1023 EOF
1024 done
1025 cat <<-EOF
1026 </table>
1027 <p><a href="?replyblog+$rowid">[$title]に戻る</a></p>
1028 </html>
1029 EOF
1031 lsmyfile() { # $1(optional)=SortBy
1032 case "$1" in
1033 ""|CTIME-DESC)
1034 by="CTIME" ord="DESC" ;;
1035 CTIME*) by="CTIME" ;;
1036 FILE*) by="FILE" ;;
1037 OWNER*) by="OWNER" ;;
1038 TITLE*) by="TITLE" ;;
1039 esac
1040 case "$1" in
1041 *DESC) ord="DESC" ;;
1042 esac
1043 case "$ord" in
1044 DESC) lkod="" jord="降順" ;;
1045 *) lkod="-DESC" jord="昇順" ;;
1046 esac
1047 sql="select m.val||'/'||m.rowid FILE,
1048 coalesce(
1049 case when (select name from user where name=bs.owner)
1050 is not null
1051 then (select val from user_s where name=bs.owner
1052 and key='gecos')
1053 when (select gname from grp where gname=bs.owner)
1054 is not null
1055 then (select val from grp_s where gname=bs.owner
1056 and key='gecos')
1057 else
1058 null
1059 end,
1060 bs.owner
1061 ) OWNER,
1062 a_s.val CTIME,
1063 ',t,'||bs.title||':'||b.rowid||'#'||a.id TITLE
1064 from (select rowid,id,val from article_m where id
1065 in (select id from article where author='$user')
1066 and type like 'file:%')
1067 m left join article a on m.id=a.id
1068 left join article_s a_s on a.id=a_s.id and a_s.key='ctime'
1069 left join (select id,
1070 max(case key when 'owner' then val end) as owner,
1071 max(case key when 'title' then val end) as title
1072 from blog_s group by id)
1073 bs on a.blogid=bs.id
1074 left join blog b on bs.id=b.id
1075 where m.val is not null order by $by $ord;"
1076 err lshandoutbyauthor: sql=`echo "$sql"`
1077 title="個人提出ファイル"
1078 _m4 -D_TITLE_=$title $layout/html.m4.html
1079 hra="<a href=\"?lsmyfile+"
1080 hrb="<a href=\"?showattc+article_m+"
1081 hrc="<a href=\"?replyblog+"
1082 (echo '<table class="b">'
1083 echo "$sql"|sq -html -header $db ) \
1084 | sed -e "s|\(<TR><TD>\)\([^/]*\)/\([0-9]*\)|\1$hrb\3\">\2</a>|" \
1085 -e "s|,t,\(.*\):\([^<]*\)\(</TD>\)|$hrc\2\">\1</a>\3|" \
1086 -e "s|\(<TH>\)\([A-Z]*\)\(</TH>\)|\1$hra\2$lkod\">\2</a>|" \
1087 | _m4 -D_TITLE_=$title -D_FORM_="<p>($by$jord)</p>" \
1088 -D_DUMPTABLE_="syscmd(cat)" $layout/form+dump.m4.html
1089 echo '</table>'
1091 getteamcsv() {
1092 gid=`numericalize "$1"`
1093 grp=`getgroupbyid "$gid"`
1094 err gid=$gid grp=$grp
1095 if ! isgrpowner "$user" "$grp"; then
1096 contentytpe 'text/plain; charset="utf-8"'; echo
1097 echo "管理者メンバー以外は利用できません。" | html p; return
1098 fi
1099 fn="team-$gid.csv"
1100 csv="$tmpd/$fn"
1101 err csv=$csv
1102 # We can leave csv mode here because this scripts will exit soon
1103 query <<-EOF
1104 .mode csv
1105 .head 1
1106 .output $csv
1107 SELECT val "ルーム名を事前割り当て", user "メールアドレス"
1108 FROM grp_mem_m
1109 WHERE key='team'
1110 AND gname=(SELECT gname FROM grp WHERE rowid=$gid)
1111 ORDER BY val;
1112 EOF
1113 contenttype 'text/plain; charset="utf-8"'
1114 echo "Content-Disposition: filename=\"$fn\""
1115 echo "Content-Length: " `cat $csv | wc -c`; echo
1116 #echo "Content-Type: " ${type#file:}; echo
1117 cat $csv
1118 exit
1120 searchart() {
1121 _m4 -D_TITLE_="検索結果" $layout/html.m4.html
1122 kwd=`getpar kwd|nkf -wZ1` # Convert Zenkaku-SPC to ASCII-SPC
1123 bloglist=`getpar bloglist|sed 's/[^0-9,]//g'`
1124 kwdgrp=""
1125 authcond=""
1126 if [ -z "$kwd" ]; then
1127 echo "検索語を指定してください" | html p; return
1128 fi
1129 if logstart "$searchlog"; then
1130 { echo "kwd=$kwd"
1131 test -n "$bloglist" && echo "bloglist=$bloglist"
1132 } >> $searchlog
1133 logend "$searchlog"
1134 fi
1135 if expr x"$kwd" : 'x#[1-9][0-9]*$' >/dev/null 1>&2; then
1136 # Like '#1234', assume as artID
1137 rowid=$((${kwd#\#} + 0)) # Force to be a number
1138 kc="ar.rowid = $rowid"
1139 else
1140 for k in `echo "$kwd" | sed "s/'/''/g"`; do # With wrap quotes
1141 ctime=""
1142 if expr x"$k" : 'x@[><= ]*[1-9][][0-9]*-[][0-9:-]*$' >/dev/null >&2; then
1143 # '@<2016-10-10' -> ctime < '2016-10-10'
1144 # '@>=2016-10-10' -> ctime >= '2016-10-10'
1145 # '@2016-10-10' -> ctime GLOB '@2016-10-10'
1146 k=${k#@}
1147 case "$k" in
1148 [\<\>]*) op=${k%%[!<>=]*}; ctime=${k##*[><= ]} ;;
1149 *) op='GLOB'; ctime="${k##*[><= ]}*" ;;
1150 esac
1151 kc=$kc${kc:+" AND "}"ctime $op '${ctime}'"
1152 # Not sure GROUP BY a.blogid is comfortable for searchers...?
1153 ##### kwdgrp=" GROUP BY a.blogid" ## Add this to lessen results
1154 elif [ x"$k" = x"@today" -o x"$k" = x"@今日" ]; then
1155 ctime=`date +%F`
1156 elif n=`expr x"$k" : 'x@\([0-9]*\)days*'` >/dev/null >&2; then
1157 ctime=`query "SELECT datetime('now', 'localtime', '-$n days');"`
1158 elif [ x"$k" = x"@week" ]; then
1159 ctime=`query "SELECT datetime('now', 'localtime', '-7 days');"`
1160 elif n=`expr x"$k" : 'x@\([0-9]*\)weeks*'` >/dev/null >&2; then
1161 n=$((n * 7))
1162 ctime=`query "SELECT datetime('now', 'localtime', '-$n days');"`
1163 elif [ x"$k" = x"@month" ]; then
1164 ctime=`query "SELECT datetime('now', 'localtime', '-1 month');"`
1165 elif n=`expr x"$k" : 'x@\([0-9]*\)months*'` >/dev/null >&2; then
1166 ctime=`query "SELECT datetime('now', 'localtime', '-$n month');"`
1167 elif [ x"$k" = x"@year" ]; then
1168 ctime=`query "SELECT datetime('now', 'localtime', '-1 year');"`
1169 elif n=`expr x"$k" : 'x@\([0-9]*\)years*'` >/dev/null >&2; then
1170 ctime=`query "SELECT datetime('now', 'localtime', '-$n year');"`
1171 fi
1172 if [ -n "$ctime" ]; then
1173 kc=$kc${kc:+" AND "}"ctime > '${ctime}'"
1174 else
1175 e=""
1176 case "$k" in
1177 *${likeesc}*) e="" ;; # Giving up char-escaping
1178 *%*|*_*) k=`echo "$k"|sed "s/\([%_]\)/${likeesc}\1/g"`
1179 e=" ESCAPE '$likeesc'" ;;
1180 esac
1181 kc=$kc${kc:+" AND "}"content LIKE '%$k%'$e"
1182 fi
1183 done
1184 fi
1185 kwd=`echo "$kwd"|htmlescape`
1186 owner=`getpar owner`
1187 owner=${owner:-$1}
1188 grid=`getpar grid`
1189 msg=""
1190 if [ -n "$grid" ]; then
1191 grp=`getgroupbyid "$grid"`
1192 qgrp=`sqlquote "$grp"`
1193 cond="WHERE key='owner' AND val=$qgrp"
1194 msg="(`linkhome $grid` グループから)"
1195 elif [ -n "$owner" ]; then
1196 cond="where key='owner' and val='$owner'"
1197 msg="(`linkhome $owner` さんの記録から)"
1198 elif { author=`getpar author`; test -n "$author"; }; then
1199 atptn=`sqlquotestr $author`
1200 #kc="$kc${kc:+ AND }author=$atptn"
1201 authcond="WHERE author=$atptn"
1202 if isuser $author; then
1203 msg="(`linkhome $author` さんの書き込みから)"
1204 fi
1205 fi
1206 if [ -n "$bloglist" ]; then
1207 blogcond="AND bl.rid IN ($bloglist)"
1208 fi
1210 sf=`search_form "$search_form_args" "$kwd" | sed '1d;$d'` # rm <div></div>
1211 echo "$sf" | sed -e "/POST SENTENCE/s/.*/__PS__/" -e "/EOF/q" \
1212 | _m4 -D__PS__="による検索結果$msg"
1213 echo "(上記入力窓で再検索すると下記の掲示板のみに絞って再検索します)" \
1214 | html p 'class="small"'
1215 # article_s: id=article-id, key='text', val='TEXT'
1216 # article: id=article-id, blogid=blogkd
1217 # blog: id=blog-id, author=LeaderAuthor
1218 # blog_s: id=blog-id, key='title', val='BLOG-TITLE'
1219 # WANT: blog-ROWid,article-id,val(TEXT)
1220 sql2="`sql4readableblogs` -- Extract user-readable blogs
1221 -- 0.3sec
1222 WITH artsm AS (
1223 SELECT a.id,ctime, text || ' ' || coalesce(files, '') content
1224 FROM article a
1225 LEFT JOIN
1226 (SELECT ars.id, ctime, text, coalesce(files, '') files
1227 FROM (SELECT id,
1228 max(CASE key WHEN 'ctime' THEN val END) ctime,
1229 max(CASE key WHEN 'text' THEN val END) text
1230 FROM article_s
1231 GROUP BY id) ars
1232 LEFT JOIN
1233 (SELECT id, group_concat(val) files
1234 FROM article_m
1235 WHERE type LIKE 'file:%'
1236 GROUP BY id) arm
1237 ON ars.id=arm.id
1238 ) ar
1239 ON a.id=ar.id
1240 ), ar AS (
1241 SELECT a.rowid, a.blogid, a.id, a.author, ctime, content
1242 FROM article a JOIN artsm ON a.id=artsm.id
1243 $authcond
1244 ), bl AS (
1245 SELECT blg.rid, blg.*, blog_s.val TITLE
1246 FROM readableblogs blg JOIN blog_s ON blg.id=blog_s.id AND blog_s.key='title'
1248 SELECT bl.rid||'+n:all#'||ar.id '',
1249 bl.title TITLE,
1250 (SELECT gecos FROM gecoses WHERE name=ar.author) AUTHOR,
1251 substr(ctime, 0, 11) DATE,
1252 substr(content, 0, 78) TEXT
1253 FROM ar JOIN bl
1254 ON ar.blogid=bl.id
1255 WHERE $kc AND bl.id IN (SELECT id FROM blog_s $cond) $blogcond
1256 ORDER by DATE DESC, TITLE, ctime;"
1257 sedopt="s,<TR><TD>\([^<]*\)</TD>,<TR><TD><a\
1258 href=\"?replyblog+\1\">VIEW</a></TD>,"
1259 # echo "$sql2" > tmp/sql.out
1260 result=$tmpd/result.$$
1261 cat<<EOF
1262 <table class="b searchart">
1263 `sq -header -html $db "$sql2"|sed "$sedopt"|tee $result`
1264 </table>
1265 EOF
1266 if [ -s "$result" ]; then
1267 found=$((`grep "^<TR><TD>" $result | wc -l` + 0)) # Cast to INT
1268 one=${found%1}
1269 echo "$found match${one:+es} found"
1270 # <a href="?replyblog+39#12345">VIEW</a>
1271 # -> 39,49,55, -> 39,49,55
1272 # -> <input type="hidden" name="bloglist" value="39,49,55">
1273 sed -n "/.*href=.*replyblog\+\([0-9][0-9]*\).*/s//\1/p" "$result" \
1274 | sort | uniq | tr '\n' ',' \
1275 | sed -e 's/,$//' \
1276 -e 's/^/<input type="hidden" name="bloglist" value="/' \
1277 -e 's/$/">/'
1278 else
1279 echo orz...
1280 fi
1281 echo "$sf" | sed "1,/-- EOF/d" # Close <form>
1283 listblog() (
1284 # $1={user,group}
1285 qow=`sqlquote "$1"`
1286 cond="where a.id in (select id from blog_s where key='owner' and val=$qow) order by ctime desc"
1287 cgi_form searchart<<EOF
1288 <label>`cgi_text kwd`という語を含む記事をこの一覧から検索</label>
1289 `cgi_hidden owner $user`
1290 EOF
1291 DT_CHLD=article:blogid DT_QOWNER=$qow \
1292 dumptable html blog 'ctime title heading' "$cond"
1295 blog_addentry() {
1296 # $1=GRProwID(if it is a group)
1297 grprowid=`numericalize $1`
1298 rowid=`getpar rowid`
1299 ## err blog_addentry0: rowid=$rowid
1300 if [ -n "$grprowid" ]; then
1301 owner=`getgroupbyid $grprowid`
1302 else
1303 owner=`getpar owner`
1304 fi
1305 htmlowner=`echo $owner|htmlescape`
1306 err blog-add: \$1=$grprowid rowid=$rowid owner=$owner
1307 if isgroup "$owner"; then
1308 if [ -z "$grprowid" ]; then
1309 qgrp=`sqlquote "$owner"` # Inefficient...
1310 grprowid=`query "SELECT rowid FROM grp WHERE gname=$qgrp;"`
1311 fi
1312 groupmode=1 listing=$owner GF_OWNER=$owner
1313 titleguide="[$owner]" guide="[`linkhome $grprowid`]"
1314 GF_ARGS=$(mvteamform "$owner")
1315 else
1316 usermode=1 listing=$user guide="[個人]" titleguide=$guide
1317 fi
1319 title=`getpar title`
1320 if [ -n "$title" ]; then
1321 if [ "$usermode" ]; then
1322 err usermode: user=$user owner=$owner
1323 if [ x"$user" != x"$owner" ]; then
1324 echo "他人の日記は書けません" | html p
1325 return 2
1326 fi
1327 elif [ "$groupmode" ]; then # if write to group log
1328 grp=$owner #\`getpar grp\`
1329 err ismember: $user $grp
1330 if ! ismember "$user" "$grp"; then
1331 echo "(話題作成はこのグループに加入してから)" | html p
1332 return 3
1333 fi
1334 fi
1335 par2table $formdir/blog.def
1336 serial=`getpar serial`
1337 ## err SERIAL: $serial ROWID=$rowid listing=$listing
1338 id=""
1339 if [ -n "$rowid" ]; then
1340 # Here, id becomes NULL when removal of entries at par2table
1341 id=`query "select rowid from blog where rowid=$rowid;"`
1342 elif [ -n "$serial" ]; then
1343 # If new blog leader created, traverse to its head.
1344 id=`query "select rowid from blog where id='$serial';"`
1345 ## err new-Leader: "select rowid from blog where id='$serial';" id=$id
1346 fi
1347 if [ -n "$id" ]; then
1348 ## If modifying existing blog, JUMP to blog_reply
1349 blog_reply $id
1350 return
1351 fi
1352 # Newly created blog comes here:
1353 mv2team=`getpar mv2team`
1354 if [ -n "$mv2team" -a -n "$groupmode" ]; then
1355 # For newly created BLOG, assign team-name if necessary and correct
1356 qmt=`sqlquote "$mv2team"`
1357 qowner=`sqlquote "$owner"`
1358 team=$(query "SELECT val FROM grp_mem_m
1359 WHERE key='team' AND val=$qmt AND gname=$qowner;")
1360 if [ -n "$team" ]; then # If it is valid team name
1361 qtt=`sqlquote "$title"`
1362 # We should acquire newly created blog id from title step by step
1363 thisblog=$(query \
1364 "SELECT id FROM blog_s
1365 WHERE id IN (SELECT id FROM blog_s
1366 WHERE key='owner' AND val=$qowner)
1367 AND key='title' AND val=$qtt;")
1368 if [ -n "$thisblog" ]; then
1369 query "REPLACE INTO blog_s(id, key, type, val)
1370 VALUES('$thisblog', 'team', 'string', $qmt);"
1371 fi
1372 fi
1373 fi
1374 fi
1375 echo "${titleguide}新規話題作成" > $tmpd/title.$$
1376 echo "${guide}新規話題作成" > $tmpd/h1.$$
1377 listblog "$listing" > $tmpd/listblog.$$
1378 genform $formdir/blog.def \
1379 | _m4 -D_TITLE_="spaste(\`$tmpd/title.$$')" \
1380 -D_H1_="spaste(\`$tmpd/h1.$$')" \
1381 -D_FORMHEAD_="序文は簡単に詳しくはコメントに" \
1382 -D_DUMPHEAD_="これまでの蓄積" \
1383 -D_FORM_="syscmd(\`cat')" \
1384 -D_DUMPTABLE_="spaste(\`$tmpd/listblog.$$')" \
1385 $layout/html.m4.html \
1386 $layout/form+dump-whead.m4.html
1389 blog_reply() { # Posting to blog article
1390 # $1=rowid $2=control-sequence
1391 rowid=`numericalize $1` # Ensure (already purified in s4.cgi)
1393 if [ -z "$rowid" ]; then
1394 echo "表示する日記番号が未指定です。" | html p
1395 return
1396 fi
1397 title=`getvalbyid blog title $rowid`
1398 owner=`getvalbyid blog owner $rowid`
1399 htmlowner=`echo $owner|htmlescape`
1400 qowner=`sqlquotestr "$owner"`
1401 if [ -z "$title" ]; then
1402 echo "日記番号指定が無効です。" | html p
1403 return
1404 fi
1405 err "blog_reply($$) Started: `gdate +%S.%03N` blogrowid=$rowid"
1406 blog_writable $rowid $user; rc=$?
1407 if [ $rc = 0 ]; then
1408 iswritable=true
1409 else
1410 iswritable=false
1411 if [ $((rc & $BLOG_FROZEN)) -gt 0 ]; then
1412 isfrozen=true
1413 frozen_class='frozen"'
1414 frozen_flag=$FROZEN_TAG
1415 fi
1416 fi
1417 if isuser "$owner"; then
1418 subtitle="`gecos $owner` さんの話題"
1419 else
1420 grprowid=`query "select rowid from grp where gname=$qowner;"`
1421 subtitle="グループ
1422 <a href=\"?grp+$grprowid\" accesskey=\"h\" title=\"H\">$htmlowner</a> での話題
1423 `query \"SELECT printf('(チーム:%s)', val)\
1424 FROM blog_s
1425 WHERE id=(SELECT id FROM blog WHERE rowid=$rowid)
1426 AND key='team';
1427 \"|htmlescape`"
1428 memclass=`grp_getbodyclass "$owner"`
1429 fi
1431 text=`getpar text`
1432 if [ -n "$text" ]; then
1433 if $iswritable; then
1434 ## BEGIN: 2020-06-11 - Post Integrity Check. Disable if it slows down..
1435 blogid=`getpar blogid | tr -c -d 'a-z0-9'`
1436 brid=`query "SELECT rowid FROM blog WHERE id='$blogid';"`
1437 if [ x"$rowid" != x"$brid" ]; then
1438 _id=`getpar id | tr -c -d 'a-z0-9'`
1439 _aid=`query "SELECT rowid FROM article WHERE id='$_id';"`
1440 if [ -z "$_aid" ]; then
1441 echo "掲示板から書き込んで下さい。" | html p
1442 return
1443 fi
1444 fi
1445 ## END:
1446 par2table $formdir/article.def
1447 st=$?
1448 err "blog_reply($$): POSTdone `gdate +%S.%03N` - st=$st title=$title owner=$owner user=$user, blogid=$blogid"
1449 case $st in
1450 0|4)
1451 [ "$st" = "4" ] && act="書込削除"
1452 blog_notify_reply $rowid $user "$text" $act
1453 if [ -n "$grprowid" ]; then
1454 qgrp=$(sqlquote "$owner")
1455 dbsetbyid grp "$owner" wtime "`date '+%F %T'`"
1456 else
1457 dbsetbyid user "$user" wtime "`date '+%F %T'`"
1458 fi
1459 ;;
1460 esac
1461 else
1462 if $isfrozen; then
1463 title="$title(凍結板につき書き込み不可)"
1464 else
1465 title="$title(加入してないので書き込み不可)"
1466 fi
1467 fi
1468 fi
1469 def=$formdir/article.def
1470 echo "$title" | htmlescape > $tmpd/title.$$
1471 echo "$subtitle$frozen_flag" > $tmpd/subtitle.$$
1472 ${BLOG_SHOW:-blog_showentry} blog $rowid "$2" \
1473 | _m4 -D_TITLE_="spaste(\`$tmpd/title.$$')" \
1474 -D_H1_="spaste(\`$tmpd/title.$$')" \
1475 -D_BODYCLASS_=general"${memclass:+ $memclass}" \
1476 -D_FORMHEAD_="spaste(\`$tmpd/subtitle.$$')" \
1477 -D_FORM_='' \
1478 -D_DUMPTABLE_="syscmd(cat)" -D_DUMPHEAD_="" \
1479 $layout/html.m4.html $layout/form+dump-whead.m4.html
1480 err "blog_reply($$) Finished: `gdate +%S.%03N` user=$user owner=[$owner] title=[$title]"
1483 blog_reply_article() { # Direct link to article in some blog
1484 arid=${1:-0} # Already sanitized to digits
1485 brid=`query "SELECT rowid FROM blog WHERE \
1486 id=(SELECT blogid FROM article WHERE rowid=$arid);"`
1487 if [ -n "$brid" ]; then
1488 newurl="?replyblog+$brid#$arid"
1489 echo "Refresh: 0; $newurl"; echo
1490 exit 0
1491 else
1492 contenttype; echo
1493 echo "無効な記事番号です." | html p
1494 fi