tinycm view index.cgi @ rev 60

Improve dashboard and plugin integration
author Christophe Lincoln <pankso@slitaz.org>
date Sat Feb 01 01:28:51 2014 +0100 (2014-02-01)
parents a85e81a401ee
children ea608c745b41
line source
1 #!/bin/sh
2 #
3 # TinyCM - Small, fast and elegent CGI/SHell Content Manager
4 #
5 # Copyright (C) 2012-2014 SliTaz GNU/Linux - BSD License
6 #
7 . /usr/lib/slitaz/httphelper
9 # Let's have a peer site config file with a .cgi extension so content
10 # is secure even if left in a web server directory.
11 . ./config.cgi
13 tiny="$PWD"
14 content="content"
15 wiki="$content/wiki"
16 index="index"
17 cache="cache"
18 plugins="plugins"
19 tmp="/tmp/tinycm"
20 sessions="$tmp/sessions"
21 script="$SCRIPT_NAME"
22 activity="$cache/log/activity.log"
24 # Content negotiation for Gettext
25 IFS=","
26 for lang in $HTTP_ACCEPT_LANGUAGE
27 do
28 lang=${lang%;*} lang=${lang# } lang=${lang%-*}
29 case "$lang" in
30 en) lang="C" && break ;;
31 fr) lang="fr_FR" && break ;;
32 pt) lang="pt_BR" && break ;;
33 ru) lang="ru_RU" && break ;;
34 esac
35 done
36 unset IFS
37 export LANG=$lang LC_ALL=$lang
39 # Internationalization
40 . /usr/bin/gettext.sh
41 TEXTDOMAIN='tinycm'
42 export TEXTDOMAIN
44 #
45 # Functions
46 #
48 # Used by edit to display language name and the language box. This is
49 # for CM content not gettext support.
50 get_lang() {
51 dlang=$(echo $d | cut -d "/" -f 1)
52 doc=${d#$dlang/}
53 echo '<div id="lang">'
54 for l in $LANGUAGES
55 do
56 case $dlang in
57 en) i18n="English" ;;
58 fr) i18n="Français" ;;
59 pt) i18n="Português" ;;
60 ru) i18n="Русский" ;;
61 *) i18n="*" ;;
62 esac
63 echo "<a href='?d=$l/$doc'>$l</a>"
64 done
65 echo '</div>'
66 }
68 # HTML 5 header.
69 html_header() {
70 if [ -f "$tiny/lib/header.html" ]; then
71 cat $tiny/lib/header.html | sed -e s!'%TITLE%'!"$TITLE - $d"!g
72 else
73 cat << EOT
74 <!DOCTYPE html>
75 <html xmlns="http://www.w3.org/1999/xhtml">
76 <head>
77 <title>$TITLE</title>
78 <meta charset="utf-8" />
79 <style type="text/css">body { margin: 40px 120px; }</style>
80 </head>
81 <body>
82 <!-- Content -->
83 <div id="content">
84 EOT
85 fi
86 }
88 # HTML 5 footer.
89 html_footer() {
90 if [ -f "$tiny/lib/footer.html" ]; then
91 cat $tiny/lib/footer.html
92 else
93 cat << EOT
95 <!-- End content -->
96 </div>
98 <div id="footer">&hearts;</div>
100 </body>
101 </html>
102 EOT
103 fi
104 }
106 # Default index if missing
107 default_index() {
108 mkdir -p "$wiki"
109 cat > $wiki/$index.txt << EOT
110 ==== Welcome ====
112 <p>
113 This is the default index page of your TinyCM, you can login then start to
114 edit and adding some content. You can read the help about text formating
115 and functions: [Help page|en/help]
116 </p>
118 EOT
119 }
121 # Log main activity.
122 log_activity() {
123 [ -d "$cache/log" ] || mkdir -p ${cache}/log
124 #gravatar="$(get_gravatar $MAIL 24)"
125 grep ^[A-Z] | \
126 sed s"#^[A-Z]\([^']*\)#$user|$(date '+%Y-%m-%d')|\0#" \
127 >> $cache/log/activity.log
128 }
130 # Log documents activity.
131 log() {
132 grep ^[A-Z] | \
133 sed s"#^[A-Z]\([^']*\)#$(date '+%Y-%m-%d %H:%M') : \0#" \
134 >> $cache/$d/activity.log
135 }
137 # Check if user is auth
138 check_auth() {
139 auth="$(COOKIE auth)"
140 user="$(echo $auth | cut -d ":" -f 1)"
141 md5cookie="$(echo $auth | cut -d ":" -f 2)"
142 [ -f "$sessions/$user" ] && md5session="$(cat $sessions/$user)"
143 if [ "$md5cookie" == "$md5session" ] && [ "$auth" ]; then
144 . $PEOPLE/$user/account.conf
145 return 0
146 else
147 return 1
148 fi
149 }
151 # Check if user is admin
152 admin_user() {
153 fgrep -q 'ADMIN_USER="yes"' ${PEOPLE}/${user}/account.conf
154 }
156 # Authentified or not
157 user_box() {
158 if check_auth; then
159 cat << EOT
161 <div id="user">
162 <a href="$script?user=$user">$(get_gravatar $MAIL 20)</a>
163 <a href="$script?logout">Logout</a>
164 </div>
166 EOT
167 else
168 cat << EOT
170 <div id="user">
171 <a href="$script?login"><img src="images/avatar.png" alt="[ User ]" /></a>
172 <a href="$script?login">Login</a>
173 </div>
175 EOT
176 fi
177 cat << EOT
178 <!--
179 <div id="search">
180 <form method="get" action="$script">
181 <input type="text" name="search" placeholder="$(gettext "Search")" />
182 </form>
183 </div>
184 -->
185 EOT
186 }
188 # Link for online signup if enabled.
189 online_signup() {
190 if [ "$ONLINE_SIGNUP" == "yes" ]; then
191 echo -n "<p><a href='$script?signup'>"
192 gettext "Create a new account"
193 echo '</a></p>'
194 fi
195 }
197 # Login page
198 login_page() {
199 cat << EOT
200 <h2>$(gettext "Login")</h2>
202 <div id="account-info">
203 $(gettext "No account yet or trouble with you account? Please send
204 a request to $ADMIN_MAIL with your real name, user name, mail and password.")
205 $(online_signup)
206 </div>
208 <div id="login">
209 <form method="post" action="$script">
210 <input type="text" name="auth" placeholder="$(gettext "User name")" />
211 <input type="password" name="pass" placeholder="$(gettext "Password")" />
212 <div>
213 <input type="submit" value="Login" /> $error
214 </div>
215 </form>
216 </div>
218 <div style="clear: both;"></div>
219 EOT
220 }
222 # Signup page
223 signup_page() {
224 cat << EOT
226 <div id="signup">
227 <form method="post" name="signup" action="$script" onsubmit="return checkSignup();">
228 <input type="hidden" name="signup" value="new" />
229 <input type="text" name="name" placeholder="$(gettext "Real name")" />
230 <input type="text" name="user" placeholder="$(gettext "User name")" />
231 <input type="text" name="mail" placeholder="$(gettext "Email")" />
232 <input type="password" name="pass" placeholder="$(gettext "Password")" />
233 <div>
234 <input type="submit" value="$(gettext "Create new account")" />
235 </div>
236 </form>
237 </div>
239 EOT
240 }
242 # Create a new user in AUTH_FILE and PEOPLE
243 new_user_config() {
244 if [ ! -f "$AUTH_FILE" ];then
245 touch $(DESTDIR)$(LOGIN)/auth/people
246 chmod 0600 $(DESTDIR)$(LOGIN)/auth/people
247 fi
248 key=$(echo -n "$user:$mail:$pass" | md5sum | awk '{print $1}')
249 echo "$user:$pass" >> $AUTH_FILE
250 mkdir -p $PEOPLE/$user/
251 cat > $PEOPLE/$user/account.conf << EOT
252 # SliTaz user configuration
253 #
255 NAME="$name"
256 USER="$user"
257 MAIL="$mail"
258 KEY="$key"
260 EOT
261 chmod 0600 $PEOPLE/$user/account.conf
262 # First created user is admin
263 if [ $(ls ${PEOPLE} | wc -l) == "1" ]; then
264 echo 'ADMIN_USER="yes"' >> $PEOPLE/$user/account.conf
265 fi
266 }
268 # Display user public profile.
269 public_people() {
270 echo "</pre>"
271 # Display personnal user profile
272 if [ -f "$PEOPLE/$USER/profile.txt" ]; then
273 cat $PEOPLE/$USER/profile.txt | wiki_parser
274 fi
275 }
277 # Display authentified user profile. TODO: change password
278 auth_people() {
279 cat << EOT
280 Email : $MAIL
281 Secure key : $KEY
282 </pre>
283 EOT
284 # Each user can have personal profile page
285 if [ -f "$PEOPLE/$USER/profile.txt" ]; then
286 cat $PEOPLE/$USER/profile.txt | wiki_parser
287 cat << EOT
288 <div id="tools">
289 <a href="$script?edit=profile">$(gettext "Edit profile")</a>
290 <a href="$script?dashboard">Dashboard</a>
291 </div>
292 EOT
293 else
294 cat << EOT
295 <div id="tools">
296 <a href="$script?edit=profile">$(gettext "Create a profile page")</a>
297 <a href="$script?dashboard">Dashboard</a>
298 </div>
299 EOT
300 fi
301 }
303 # The CM style parser. Just a title, simple text formating and internal
304 # links, as well as images and use HTML for other stuff. Keep it fast!
305 # To make TinyCM as easy as possible we have a small HTML editor/helper
306 # written in Javascript
307 wiki_parser() {
308 doc="[0-9a-zA-Z\.\#/~\_%=\?\&,\+\:@;!\(\)\*\$'\-]*"
309 sed \
310 -e s"#====\([^']*\)====#<h2>\1</h2>#"g \
311 -e s"#===\([^']*\)===#<h3>\1</h3>#"g \
312 -e s"#==\([^']*\)==#<h4>\1</h4>#"g \
313 -e s"#\*\*\([^']*\)\*\*#<b>\1</b>#"g \
314 -e s"#''\([^']*\)''#<em>\1</em>#"g \
315 -e s"#__\([^']*\)__#<u>\1</u>#"g \
316 -e s"#\[\([^]]*\)|\($doc\)\]#<a href='$script?d=\2'>\1</a>#"g \
317 -e s"#\[\([^]]*\)!\($doc\)\]#<a href='\2'>\1</a>#"g \
318 -e s"#\[\(http://*[^]]*.png\)\]#<img src='\1' />#"g \
319 -e s"#\[\([^]]*.png\)\]#<img src='content/cloud/\1' />#"g
320 }
322 link_user() {
323 echo "<a href='$(basename $script)?user=$user'>$user</a>"
324 }
326 # Save a document. Do we need more than 1 backup and diff ?
327 save_document() {
328 mkdir -p $cache/$d $(dirname $wiki/$d)
329 # May be a new page.
330 if [ ! -f "$wiki/$d.txt" ]; then
331 new=0
332 touch $wiki/$d.txt
333 fi
334 cp $wiki/$d.txt $cache/$d/last.bak
335 sed "s/$(echo -en '\r') /\n/g" > $wiki/$d.txt << EOT
336 $(GET content)
337 EOT
338 diff $cache/$d/last.bak $wiki/$d.txt > $cache/$d/last.diff
339 # Log
340 if [ "$new" ]; then
341 echo "Page created by: $(link_user)" | log
342 echo "New document: <a href='$script?d=$d'>$d</a>" | log_activity
343 if [ "$HG" == "yes" ]; then
344 cd $content && hg -q add
345 hg commit -q -u "$NAME <$MAIL>" -m "Created new document: $d"
346 cd $tiny
347 fi
348 else
349 # Here we may clean log: cat && tail -n 40
350 echo "Page edited by: $(link_user)" | log
351 if [ "$HG" == "yes" ]; then
352 cd $content && hg commit -q -u "$NAME <$MAIL>" \
353 -m "Edited document: $d"
354 cd $tiny
355 fi
356 fi
357 }
359 # Save a user profile.
360 save_profile() {
361 path="$PEOPLE/$user"
362 cp -f ${path}/${d}.txt ${path}/${d}.bak
363 sed "s/$(echo -en '\r') /\n/g" > ${path}/${d}.txt << EOT
364 $(GET content)
365 EOT
366 }
368 # CM tools (edit, diff, etc) for auth users
369 wiki_tools() {
370 if check_auth; then
371 cat << EOT
372 <div id="tools">
373 <a href="$script?edit=$d">$(gettext "Edit document")</a>
374 <a href="$script?log=$d">$(gettext "File log")</a>
375 <a href="$script?diff=$d">$(gettext "Last diff")</a>
376 $PLUGINS_TOOLS
377 EOT
378 [ "$HG" == "yes" ] && echo "<a href='$script?hg'>Hg Log</a>"
379 echo "</div>"
380 fi
381 }
383 # Built-in tools such as log/ls and PLUGINS_TOOLS
384 tiny_tools() {
385 if check_auth; then
386 cat << EOT
387 <div id='tools'>
388 <a href='$script?log'>Activity log</a>
389 <a href='$script?ls'>Pages list</a>
390 $PLUGINS_TOOLS
391 </div>
392 EOT
393 fi
394 }
396 # Get and display Gravatar image: get_gravatar email size
397 # Link to profile: <a href="http://www.gravatar.com/$md5">...</a>
398 get_gravatar() {
399 email=$1
400 size=$2
401 [ "$size" ] || size=48
402 url="http://www.gravatar.com/avatar"
403 md5=$(md5crypt $email)
404 echo "<img src='$url/$md5?d=identicon&s=$size' alt='&lowast;' />"
405 }
407 # List hg logs
408 hg_log() {
409 cd $content
410 cat << EOT
411 <table>
412 <thead>
413 <td>$(gettext "User")</td>
414 <td>$(gettext "Description")</td>
415 <td>$(gettext "Revision")</td>
416 </thead>
417 EOT
418 hg log --template "<tr><td>{author}</td><td>{desc}</td><td>{rev}</td></tr>\n"
419 echo '</table>'
420 }
422 #
423 # POST actions
424 #
426 case " $(POST) " in
427 *\ auth\ *)
428 # Authenticate user. Create a session file in $sessions to be used
429 # by check_auth. We have the user login name and a peer session
430 # md5 string in the COOKIE.
431 user="$(POST auth)"
432 pass="$(md5crypt "$(POST pass)")"
433 valid=$(fgrep "${user}:" $AUTH_FILE | cut -d ":" -f 2)
434 if [ "$pass" == "$valid" ] && [ "$pass" != "" ]; then
435 md5session=$(echo -n "$$:$user:$pass:$$" | md5sum | awk '{print $1}')
436 [ -d $sessions ] || mkdir -p $sessions
437 date '+%Y-%m-%d' > ${PEOPLE}/${user}/last
438 echo "$md5session" > $sessions/$user
439 header "Location: $script" \
440 "Set-Cookie: auth=$user:$md5session; HttpOnly"
441 else
442 header "Location: $script?login&error"
443 fi ;;
444 *\ signup\ *)
445 # POST action for signup
446 name="$(POST name)"
447 user="$(POST user)"
448 mail="$(POST mail)"
449 pass="$(md5crypt "$(POST pass)")"
450 if ! grep "^${user}:" $AUTH_FILE; then
451 new_user_config
452 header "Location: $script?login"
453 else
454 header
455 html_header
456 user_box
457 echo "<h2>$(gettext 'User already exists:') $user</h2>"
458 html_footer
459 fi ;;
460 esac
462 #
463 # Plugins
464 #
465 for p in $(ls -1 $plugins)
466 do
467 [ -f "$plugins/$p/$p.conf" ] && . $plugins/$p/$p.conf
468 [ -x "$plugins/$p/$p.cgi" ] && . $plugins/$p/$p.cgi
469 done
471 #
472 # GET actions
473 #
475 case " $(GET) " in
476 *\ edit\ *)
477 d="$(GET edit)"
478 header
479 html_header
480 user_box
481 get_lang
482 wiki_tools
483 if check_auth; then
484 if [ "$doc" == "profile" ]; then
485 wiki="$PEOPLE/$user"
486 fi
487 cat << EOT
488 <h2>$(gettext "Edit $doc [ $i18n ]")</h2>
490 <div id="edit">
492 <form method="get" action="$script" name="editor">
493 <input type="hidden" name="save" value="$d" />
494 <textarea name="content">$(cat "$wiki/$d.txt")</textarea>
495 <input type="submit" value="$(gettext "Save document")" />
496 $(gettext "Code Helper:")
497 $(cat lib/jseditor.html)
498 </form>
500 </div>
501 EOT
502 else
503 gettext "You must be logged in to edit pages"
504 fi
505 html_footer ;;
507 *\ save\ *)
508 d="$(GET save)"
509 if check_auth; then
510 # User profile
511 if [ "$d" == "profile" ]; then
512 save_profile
513 header "Location: $script?user=$user"
514 else
515 save_document
516 fi
517 fi
518 header "Location: $script?d=$d" ;;
520 *\ log\ *)
521 d="$(GET log)"
522 header
523 html_header
524 user_box
525 # Main activity
526 if [ "$d" == "log" ]; then
527 tiny_tools
528 echo "<h2>$(gettext "Activity log")</h2>"
529 echo '<pre>'
530 if [ -f "$cache/log/activity.log" ]; then
531 IFS="|"
532 tac $cache/log/activity.log | while read USER DATE LOG
533 do
534 . ${PEOPLE}/${USER}/account.conf
535 cat << EOT
536 <a href='$script?user=$USER'>$(get_gravatar $MAIL 24)</a>\
537 <span class='date'>$DATE -</span> $LOG
538 EOT
539 done
540 unset IFS
541 else
542 gettext "No activity log yet"; echo
543 fi
544 echo '</pre>'
545 html_footer && exit 0
546 fi
547 # Document activity
548 get_lang
549 wiki_tools
550 echo "<h2>$(gettext "Activity for:") <a href='$script?d=$d'>$d</a></h2>"
551 echo '<pre>'
552 if [ -f "$cache/$d/activity.log" ]; then
553 tac $cache/$d/activity.log
554 else
555 gettext "No log for: $d"; echo
556 fi
557 echo '</pre>'
558 html_footer ;;
560 *\ ls\ *)
561 d="Document list"
562 header
563 html_header
564 user_box
565 tiny_tools
566 [ ! check_auth ] && auth=0
567 echo "<h2>$(gettext "Pages list")</h2>"
568 echo '<pre>'
569 cd ${wiki}
570 for d in $(find . -type f | sed s'/.\///')
571 do
572 echo "<a href='$script?d=${d%.txt}'>${d%.txt}</a>"
573 [ "$auth" ] && cat << EOT
574 : <a href="$script?rm=$d">$(gettext "Remove")</a> || \
575 <a href="$script?edit=$d">$(gettext "Edit")</a>
576 EOT
577 done && unset auth
578 echo '</pre>'
579 html_footer ;;
581 *\ rm\ *)
582 [ ! check_auth ] && exit 1
583 d="$(GET rm)"
584 rm ${wiki}/"${d}"
585 rm -rf ${cache}/"${d%.txt}"
586 header "Location: $script?ls" ;;
588 *\ diff\ *)
589 d="$(GET diff)"
590 date="last"
591 header
592 html_header
593 user_box
594 get_lang
595 wiki_tools
596 echo "<h2>$(gettext "Diff for:") <a href='$script?d=$d'>$d</a></h2>"
597 echo '<pre>'
598 if [ -f "$cache/$d/$date.diff" ]; then
599 cat $cache/$d/$date.diff | sed \
600 -e 's|&|\&amp;|g' -e 's|<|\&lt;|g' -e 's|>|\&gt;|g' \
601 -e s"#^-\([^']*\).#<span style='color: red;'>\0</span>#"g \
602 -e s"#^+\([^']*\).#<span style='color: green;'>\0</span>#"g \
603 -e s"#@@\([^']*\)@@#<span style='color: blue;'>@@\1@@</span>#"g
604 else
605 gettext "No diff for:"; echo " $d"
606 fi
607 echo '</pre>'
608 html_footer ;;
610 *\ login\ *)
611 # The login page
612 d="Login"
613 [ "$(GET error)" ] && \
614 error="<p class="error">$(gettext "Bad login or pass")</p>"
615 header
616 html_header
617 user_box
618 login_page
619 html_footer ;;
621 *\ signup\ *)
622 # The login page
623 d="$(gettext "Sign Up")"
624 header
625 html_header
626 user_box
627 echo "<h2>$d</h2>"
628 if [ "$ONLINE_SIGNUP" == "yes" ]; then
629 signup_page
630 else
631 gettext "Online registration is disabled"
632 fi
633 html_footer ;;
635 *\ logout\ *)
636 # Set a Cookie in the past to logout.
637 expires="Expires=Wed, 01-Jan-1980 00:00:00 GMT"
638 if check_auth; then
639 rm -f "$sessions/$user"
640 header "Location: $script" "Set-Cookie: auth=none; $expires; HttpOnly"
641 fi ;;
643 *\ user\ *)
644 # User profile
645 d="$(GET user)"
646 last="$(cat $PEOPLE/"$(GET user)"/last)"
647 header
648 html_header
649 user_box
650 . $PEOPLE/"$(GET user)"/account.conf
651 cat << EOT
652 <h2>$(get_gravatar $MAIL) $NAME</h2>
654 <pre>
655 $(gettext "User name :") $USER
656 $(gettext "Last login :") $last
657 EOT
658 if check_auth && [ "$(GET user)" == "$user" ]; then
659 auth_people
660 else
661 # check_auth will set VARS to current logged user: re-source
662 . $PEOPLE/"$(GET user)"/account.conf
663 public_people
664 fi
665 html_footer ;;
667 *\ hg\ *)
668 d="Hg Log"
669 header
670 html_header
671 user_box
672 [ "$HG" != "yes" ] && gettext "Hg is disabled" && exit 0
673 [ ! -x /usr/bin/hg ] && gettext "Hg is not installed" && exit 0
674 echo "<h2>$d</h2>"
675 case " $(GET hg) " in
676 *\ init\ *)
677 if check_auth; then
678 [ -d "$content/.hg" ] && exit 0
679 echo '<pre>'
680 gettext "Executing: hg init"; echo
681 cd $content/ && hg init
682 echo '[hooks]' > .hg/hgrc
683 echo 'incoming = hg update' >> .hg/hgrc
684 gettext "Adding current content and committing"; echo
685 [ ! -f "$wiki/index.txt" ] && default_index
686 hg add && hg commit -u "$NAME <$MAIL>" \
687 -m "Initial commit with current content"
688 echo '</pre>' && cd ..
689 fi ;;
690 esac
691 hg_log
692 html_footer ;;
694 *)
695 # Display requested page
696 d="$(GET d)"
697 [ "$d" ] || d=$index
698 header
699 html_header
700 user_box
701 get_lang
703 # Generate a default index on first run
704 if [ ! -f "$wiki/$index.txt" ]; then
705 if ! default_index; then
706 echo "<pre class='error'>Directory : content/ is not writable</pre>"
707 html_footer && exit 0
708 fi
709 fi
711 # Check cache dir
712 if [ ! -w "$cache" ]; then
713 echo "<pre class='error'>Directory : cache/ is not writable"
714 echo "Command : install -m 0777 -d $tiny/cache</pre>"
715 html_footer && exit 0
716 fi
718 # Hg warning if enabled but not initiated
719 if [ "$HG" == "yes" ] && [ ! -d "$content/.hg" ]; then
720 echo '<p class="error box">'
721 gettext "Mercurial is enabled but no repository found"
722 echo ": <a href='$script?hg=init'>Hg init</a>"
723 echo '</p>'
724 fi
726 # Wiki tools
727 wiki_tools
729 # Wiki document
730 if [ ! -f "$wiki/$d.txt" ]; then
731 echo "<h2>$d</h2>"
732 gettext "The document does not exist. You can create it or read the"
733 echo " <a href='$script?d=en/help'>help</a>"
734 else
735 if fgrep -q [NOWIKI] $wiki/$d.txt; then
736 cat $wiki/$d.txt | sed '/\[NOWIKI\]/'d
737 else
738 cat $wiki/$d.txt | wiki_parser
739 fi
740 fi
741 html_footer ;;
742 esac
744 exit 0