From: Paul Hänsch Date: Sat, 6 Feb 2021 22:48:04 +0000 (+0100) Subject: merge from cgilite X-Git-Url: https://git.plutz.net/?p=confetti;a=commitdiff_plain;h=76c1e7bff1a8604ef2ef7da5d274d0db0e639139;hp=90288ab07bb1ec83a91581fadc674a87a250a853 merge from cgilite --- diff --git a/.gitmodules b/.gitmodules index fb39709..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "shcgi"] - path = shcgi - url = http://git.plutz.net/git/shcgi diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24781a9 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +.PHONY: _subtrees + +_subtrees: _cgilite + +cgilite: + git subtree add --squash -P $@ https://git.plutz.net/git/$@ master + +_cgilite: cgilite + git subtree pull --squash -P $< https://git.plutz.net/git/$< master diff --git a/actions/export_vcard.sh b/actions/export_vcard.sh deleted file mode 100755 index d6ea293..0000000 --- a/actions/export_vcard.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/zsh - -# Copyright 2014,2015 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -echo -n "Content-Type: text/vcard;charset=utf-8\n\n" -card="${_GET[card]}" -cat "$_DATA/vcard/$card" diff --git a/actions/generate_courselist.sh b/actions/generate_courselist.sh deleted file mode 100755 index d777536..0000000 --- a/actions/generate_courselist.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/zsh - -# Copyright 2014, 2016, 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -pdflatex="$(where pdflatex |head -n1 || echo false)" -course="${_GET[course]}" -fromdate="${_GET[fromdate]}" -fromdate="$(date -d "$fromdate" +%s)" 2>/dev/null -[ -z "$fromdate" ] && fromdate=$(date +%s) - -. ${_EXEC}/pages/courses.sh -. ${_EXEC}/pages/cards.sh - -tex_clean() { #in dire need for improvement - printf %s "$*" |tr -d '{&}\\"' -} - - -list_attendee() { #Parameter: Cardfile - id="$1" - cardfile="$_DATA/vcard/${id}" - - declare -A values - - if [ -r "$cardfile" ]; then - vcf_parse "$cardfile" - - n=$(printf %s "$values[N]" \ - | sed -rn 's:^([^;]*)(;[^;]*)(;[^;]*)?(;[^;]*)?(;[^;]*)?$:\4 \2 \3 \1 \5:gp' \ - | sed -r 's:,: :;s:;: :g;s: +: :g;s:^ $::;' - ) - fullname="${n:-${values[FN]:-${values[NICKNAME]}}}" - - tel='' - for n in TEL TEL{0..10}; do if (echo "$values[$n]" |grep -Eq '[0-9]'); then - [ -n "$tel" ] && tel="$tel\\newline $(tex_clean "$values[$n]")" || tel="$(tex_clean "$values[$n]")" - fi; done - - note='' - for n in NOTE NOTE{0..10}; do if [ -n "$values[$n]" ]; then - [ -n "$note" ] && note="$note\\newline $(tex_clean "$values[$n]")" || note="$(tex_clean "$values[$n]")" - fi; done - printf '%s & %s & %s & %s\n' \ - "$(tex_clean $fullname)" "$(tex_clean $values[BDAY])" "$tel" "$note" \ - | sed -r ':X;N;$!bX; s;\n;\\newline ;g' - fi -} - -get_dates() { #Parameter: Calendarfile - calendarfile="$_DATA/ical/$course" - - declare -A values - ics_parse "$calendarfile" - - dtstart="$values[DTSTART]" - [ -z "$dtstart" ] && dtstart=$(date +%Y%m%dT%H%M%S) - echo "$dtstart" |case "$dtstart" in - *Z) sed -rn 's:^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$:\1-\2-\3 \4\:\5\:\6 UTC:p';; - TZID*) sed -rn 's:^TZID=(.+)\:([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$:TZ="\1" \2-\3-\4 \5\:\6\:\7:p';; - *) sed -rn 's:^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$:\1-\2-\3 \4\:\5\:\6:p';; - esac |read dts_date - rrule="$values[RRULE]" - rr_int="$(echo $rrule |sed -rn 's:^.*INTERVAL=([0-9]+)(;.*)?$:\1:p')" - rr_freq="$(echo $rrule |sed -rn 's:^.*FREQ=(YEARLY|MONTHLY|WEEKLY|DAILY)(;.*)?$:\1:p')" - case "$rr_freq" in - YEARLY) rec="$rr_int year";; - MONTHLY) rec="$rr_int month";; - DAILY) rec="$rr_int day";; - *) rec="$rr_int week";; - esac - - next_date="$dts_date" - n=10 - while [ $n -gt 0 ]; do - if [ "$(date -d "$next_date" +%s)" -gt "$(date +%s)" ]; then - dtlist="$dtlist & $(date -d "$next_date" +"%d. %b.")" - n=$(($n - 1)) - fi - next_date="$(date -d "$next_date + $rec" +%Y-%m-%d)" - done - - echo "$dtlist" -} - -if [ -r "${_DATA}/ical/${course}" ]; then - . ${_EXEC}/templates/course_print.sh >"${_DATA}/temp/courselist_${course}.tex" - [ -e "${_DATA}/temp/courselist_${course}.pdf" ] && rm "${_DATA}/temp/courselist_${course}.pdf" - "$pdflatex" -halt-on-error -output-directory "${_DATA}/temp/" "${_DATA}/temp/courselist_${course}.tex" |debug >/dev/null - "$pdflatex" -halt-on-error -output-directory "${_DATA}/temp/" "${_DATA}/temp/courselist_${course}.tex" |debug >/dev/null -fi -if [ -r "${_DATA}/temp/courselist_${course}.pdf" ]; then - echo 'Content-Type: application/x-pdf\n' - cat "${_DATA}/temp/courselist_${course}.pdf" -fi diff --git a/actions/new_card.sh b/actions/new_card.sh deleted file mode 100755 index 2d6c39b..0000000 --- a/actions/new_card.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/zsh - -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -cgi_post -cgi_refdata - -filter="&filter=${_REF[filter]}" -filtertype="&filtertype=${_REF[filtertype]}" -order="&order=${_REF[order]}" - -uid=$(uuidgenerator) -card="${uid}.vcf" - -tempfile="$_DATA/temp/$card" - -cat >"$tempfile" <. - -cgi_refdata - -client="${_POST[client]:-${_GET[client]}}" - -uid=$(uuidgenerator) -prescription="${client%.vcf}.${uid}.mpx" - -cardfile="$_DATA/vcard/$client" -tempfile="$_DATA/temp/$prescription" - -cat >"$tempfile" <. - -bmfile="${_DATA}/mappings/bookmarks" - url="$(validate "${_POST[bm_url]}" '/.+' '/')" -name="$(validate "${_POST[bm_name]}" '.+' "$url")" - -case "${_POST[submit]}" in - add) printf '%s\t%s\n' "${url}" "${name}" >>"${bmfile}" - ;; - del) cp "${bmfile}" "${bmfile}.temp" - grep -vF "${url} ${name}" "${bmfile}.temp" >"${bmfile}" - rm "${bmfile}.temp" - ;; -esac - -redirect "${url}#CONFIGURE" diff --git a/actions/update_card.sh b/actions/update_card.sh deleted file mode 100755 index a1143c3..0000000 --- a/actions/update_card.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/zsh - -# Copyright 2014, 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -cgi_refdata - -filter="&filter=${_REF[filter]}" -filtertype="&filtertype=${_REF[filtertype]}" -order="&order=${_REF[order]}" - -card="${_POST[card]}" -tempfile="$_DATA/temp/$card" -cardfile="$_DATA/vcard/$card" -attfile="$_DATA/mappings/attendance" - -vcf_escape(){ - for each in "$@"; do - printf %s\\n "$each" \ - | sed -r ':X;$!{N;bX}; s;\r\n;\n;g; s;([;,\\]);\\\1;g; s;\n;\\n;g;' - done \ - | sed -r ':X;$!{N;bX}; s;\n;\;;g' -} - -[ "${_POST[hi_select]}" = "list" ] || _POST[hi_company]="${_POST[hi_other]}" -[ -n "${_POST[hi_company]}${_POST[hi_number]}${_POST[hi_status]}" ] \ -&& _POST[X-HEALTH-INSURANCE]="$(vcf_escape "${_POST[hi_company]}" "${_POST[hi_number]}" "${_POST[hi_status]}")" - -sed -r 's;$;\r;' >"$tempfile" <>"$attfile" - sed -rn 's:^(.+)'$card'$:\1:p' "$attfile" |while read course; do - touch "$_DATA/ical/$course" - done - - mv "$tempfile" "$cardfile" - redirect "?p=cards${filter}${filtertype}${order}#$card" - ;; - cancel) - rm "$tempfile" - [ -f "$cardfile" ] \ - && redirect "?p=cards${filter}${filtertype}${order}#$card" \ - || redirect "?p=cards${filter}${filtertype}${order}" - ;; - delete) - rm "$tempfile" "$cardfile" - redirect "?p=cards${filter}${filtertype}${order}" - ;; -esac diff --git a/actions/update_course.sh b/actions/update_course.sh deleted file mode 100755 index a5ba8ce..0000000 --- a/actions/update_course.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/zsh - -# Copyright 2014, 2015 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -cgi_post - -course="${_POST[course]}" -tempfile="temp/$course" -coursefile="ical/$course" -attfile="$_DATA/mappings/attendance" - -# DURATION: -uid="${_POST[UID]}" - -tzid=$(cat /etc/timezone) -tstamp=$(TZ="$tzid" date +%Y%m%dT%H%M%S) - -dts_year="${_POST[DTSYEAR]}" -dts_month="${_POST[DTSMONTH]}" -dts_day="${_POST[DTSDAY]}" -[ -n "${_POST[DTSDAY0]}" ] && dts_day="${_POST[DTSDAY0]}" -dts_hour="${_POST[DTSHOUR]}" -dts_minute="${_POST[DTSMINUTE]}" - -[ -z $dts_year ] && dts_year=$(date +%Y) -[ -z $dts_month ] && dts_month=$(date +%m) -[ -z $dts_day ] && dts_day=$(date +%d) -date -d ${dts_year}-${dts_month}-${dts_day} >/dev/null 2>/dev/null || dts_day="01" -[ -z $dts_hour ] && dts_hour=$(date +%H) -[ -z $dts_minute ] && dts_minute=$(date +%M) - -dtstart="TZID=${tzid}:${dts_year}${dts_month}${dts_day}T${dts_hour}${dts_minute}00" - -rr_int="${_POST[RRULE_INTERVAL]}" -rr_freq="${_POST[RRULE_FREQ]}" -rr_limit="${_POST[RRULE_LIMIT]}" -case "$rr_limit" in - ETERN) - rrule="FREQ=$rr_freq;INTERVAL=$rr_int" - ;; - COUNT) - t="${_POST[RRULE_COUNT]}" - rrule="FREQ=$rr_freq;INTERVAL=$rr_int;COUNT=$t" - ;; - UNTIL) - uy="${_POST[RRULE_UYEAR]}" - um="${_POST[RRULE_UMONTH]}" - ud="${_POST[RRULE_UDAY]}" - rrule="FREQ=$rr_freq;INTERVAL=$rr_int;UNTIL=${uy}${um}${ud}T000000Z" - ;; -esac - -echo "BEGIN:VCALENDAR\r" >"$tempfile" -echo "VERSION:2.0\r" >>"$tempfile" -echo "PRODID:Berlin RAW Confetti\r" >>"$tempfile" -echo "BEGIN:VEVENT\r" >>"$tempfile" -echo "UID:$uid\r" >>"$tempfile" -echo "DTSTAMP:TZID=${tzid}:${tstamp}\r" >>"$tempfile" -echo "DTSTART:${dtstart}\r" >>"$tempfile" -echo "RRULE:${rrule}\r" >>"$tempfile" -for field in SUMMARY COMMENT; do - value="${_POST[$field]}" - n=0 - while [ -n "$value" ]; do - value="$(echo "$value" |sed -r ':a;N;$!ba;s:\n:\\\\n:g;s:\r:\\\\r:g')" - echo "${field}:${value}\r" - value="${_POST[$field$n]}" - n=$(($n + 1)) - done -done >>"$tempfile" - -case "${_POST[action]}" in - addfield) - echo "${_POST[newfield]}:\r" >>"$tempfile" - echo "END:VEVENT\r" >>"$tempfile" - echo "END:VCALENDAR\r" >>"$tempfile" - echo -n "Location: ?p=courses&edit=$course\n\n" - ;; - update) - attendance=() - for att in attendance attendance{0..100}; do - [ -n "${_POST[$att]}" ] && attendance+=("${_POST[$att]}") - done - sed -rn 's:^'$course'\t(.+)$:\1:p' "$attfile" |while read card; do - touch "$_DATA/vcard/$card" - done - sed -i -r '/^'$course'\t(.+)$/d' "$attfile" - for each in $attendance; do - echo "$course\t$each" - done >>"$attfile" - sed -rn 's:^'$course'\t(.+)$:\1:p' "$attfile" |while read card; do - touch "$_DATA/vcard/$card" - done - - echo "END:VEVENT\r" >>"$tempfile" - echo "END:VCALENDAR\r" >>"$tempfile" - mv "$tempfile" "$coursefile" - echo -n "Location: ?p=courses#$course\n\n" - ;; - cancel) - rm "$tempfile" - [ -f "$coursefile" ] \ - && echo -n "Location: ?p=courses#$course\n\n" \ - || echo -n "Location: ?p=courses\n\n" - ;; - delete) - rm "$tempfile" "$coursefile" - echo -n "Location: ?p=courses\n\n" - ;; - *) - echo "END:VEVENT\r" >>"$tempfile" - echo "END:VCALENDAR\r" >>"$tempfile" - echo -n "Location: ?p=courses&edit=$course\n\n" - ;; -esac diff --git a/actions/update_prescription.sh b/actions/update_prescription.sh deleted file mode 100755 index aeb4993..0000000 --- a/actions/update_prescription.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/zsh - -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -BR=' -' -prescription="${_POST[prescription]}" - -tempfile="$_DATA/temp/$prescription" -prescfile="$_DATA/prescriptions/$prescription" -client="${prescription%.*.mpx}.vcf" -clientfile="$_DATA/vcard/$client" - -if [ -z "$prescription" -o \! -f "$clientfile" ]; then - redirect "?p=error" - exit 0 -fi - -[ "$_POST[issuer_select]" = "other" ] && _POST[issuer]="${_POST[issuer_other]}" - -# serialize POST array into file -for key in ${(k)_POST}; do - printf %s:%s\\n "$key" "${_POST[$key]//$BR/\\n}" -done >"$tempfile" - -case "${_POST[action]}" in - save) - mv "$tempfile" "$prescfile" - touch "$clientfile" - ;; - cancel) - rm "$tempfile" - ;; - delete) - rm "$tempfile" "$prescfile" - touch "$clientfile" - ;; -esac - -redirect "?p=prescriptions&client=${client}#${prescription}" diff --git a/actions/update_therapy.sh b/actions/update_therapy.sh deleted file mode 100755 index 6ba06cb..0000000 --- a/actions/update_therapy.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/zsh - -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -BR=' -' -tpy="${_POST[id]}" - -tpyfile="$_DATA/therapies/$tpy" -tempfile="$_DATA/temp/$tpy" - -# serialize POST array into file -for key in ${(k)_POST}; do - [ "$key" != imagedata ] && printf %s:%s\\n "$key" "${_POST[$key]//$BR/\\n}" -done >"$tempfile" - -if [ -n "$_POST[delete_session]" ]; then - n="$_POST[delete_session]" - sed -i -r '/^session'$n'[_:]/d' "$tempfile" - rm "${tpyfile%.tpy}_session${n}.png" - - while grep -Eq '^session'$(($n + 1))'_' "$tempfile"; do - sed -i -r 's;^session'$(($n + 1))'(_|:);session'$n'\1;' "$tempfile" - mv "${tpyfile%.tpy}_session$(($n+1)).png" "${tpyfile%.tpy}_session${n}.png" - n=$(($n+1)) - done - -elif [ -n "$_POST[new_session]" ]; then - sid="$_POST[new_session]" - - identify "$_EXEC/static/therapy_background.png" \ - | sed -r 's;^.* ([0-9]+x[0-9]+) .*$;\1;' \ - | read dim - - convert -size "$dim" xc:transparent "${tpyfile%.tpy}_${sid}.png" - - printf '%s:exists\n' "$sid" >>"$tempfile" - printf '%s_open:checked\n' "$sid" >>"$tempfile" - -elif [ -n "$_POST[imagedata]" ]; then - sed -rn 's;^(session[0-9]+)_open:checked$;\1;p' "$tempfile" \ - | sort -n \ - | tail -n1 \ - | read sid - - convert "${tpyfile%.tpy}_${sid}.png" \ - -strokewidth 2 -fill '#00000000' \ - -draw "${_POST[imagedata]}" -transparent white \ - "${tpyfile%.tpy}_${sid}.png" - sync -fi - -mv "$tempfile" "$tpyfile" - -redirect "?p=therapy&id=${tpy}" diff --git a/actions/filter_card.sh b/cards/edit_card.sh similarity index 57% rename from actions/filter_card.sh rename to cards/edit_card.sh index cc0f912..4239cc5 100755 --- a/actions/filter_card.sh +++ b/cards/edit_card.sh @@ -1,6 +1,6 @@ -#!/bin/zsh +#!/bin/sh -# Copyright 2014, 2017 Paul Hänsch +# Copyright 2019 Paul Hänsch # # This file is part of Confetti. # @@ -17,17 +17,20 @@ # You should have received a copy of the GNU Affero General Public License # along with Confetti. If not, see . -cgi_post +locktimeout=900 +. "$_EXEC"/session_lock.sh -if [ "${_POST[choice]}" = new_filter ]; then - filter="$( - for n in filter{0..100}; do - # [ -z "${_POST[$n]:+x}" ] && break - printf %s "${_POST[$n]}" - done \ - | sed -r 's;\|+;\|;g; s;\^+;\^;g; s;:\|;:;g; :X; s;\^[^:]*:\^;\^;g; /\^[^:]*:\^/bX; s;^\^;;; s;\^[^:]*:$;;;' - )" - printf 'Location: ?p=cards&order=%s&filter=%s\n\n' "${_POST[order]}" "$filter" +card="$(GET card |PATH)" +cardfile="$_DATA/vcard/${card##*/}" +filter="$(REF f)" +order="$(REF o)" + +if tempfile="$(SLOCK "$cardfile" "$locktimeout")"; then + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" +elif [ -f "$tempfile" ]; then + SET_COOKIE session message="SESSLOCK" + REDIRECT "/cards/?o=${order}&f=${filter}#${card}" else - printf 'Location: ?p=cards\n\n' + SET_COOKIE session message="EDITLOCK" + REDIRECT "/cards/?o=${order}&f=${filter}#${card}" fi diff --git a/actions/edit_card.sh b/cards/export_card.sh similarity index 69% rename from actions/edit_card.sh rename to cards/export_card.sh index f90b326..0918032 100755 --- a/actions/edit_card.sh +++ b/cards/export_card.sh @@ -1,6 +1,6 @@ -#!/bin/zsh +#!/bin/sh -# Copyright 2014 Paul Hänsch +# Copyright 2014, 2015, 2021 Paul Hänsch # # This file is part of Confetti. # @@ -17,11 +17,12 @@ # You should have received a copy of the GNU Affero General Public License # along with Confetti. If not, see . -cgi_refdata +card="$(GET card |PATH)" +cardfile="$_DATA/vcard/${card##*/}" -card="${_GET[card]}" -filter="&filter=${_REF[filter]}" -filtertype="&filtertype=${_REF[filtertype]}" -order="&order=${_REF[order]}" +. $_EXEC/pdiread.sh +. $_EXEC/cgilite/file.sh -echo -n "Location: ?p=cards${filter}${filtertype}${order}&edit=$card\n\n" +printf 'Content-Disposition: inline; filename="%s.vcf"\r\n' "$(pdi_value "$(pdi_load "$cardfile")" FN)" + +FILE "$cardfile" "text/vcard; charset=utf-8" diff --git a/cards/export_csv.sh b/cards/export_csv.sh new file mode 100755 index 0000000..9ba8993 --- /dev/null +++ b/cards/export_csv.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +. $_EXEC/pdiread.sh +. $_EXEC/cards/l10n.sh +. $_EXEC/cards/list.sh + +upcase=' y;abcdefghijklmnopqrstuvwxyzäöüé;ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜÉ;; ' + +filter="$(GET f)" +order="$(GET o)" + +[ "$order" ] || order=firstname + +list_attendance() { + grep -F " ${cardfile##*/}" "$_DATA/mappings/attendance" |while read each discard; do + { pdi_value "$(pdi_load "$_DATA/ical/$each")" SUMMARY || l10n "(unnamed course)"; } |unescape + done \ + | sed -E 's;";\\";g;' +} + +list_item() { + local item="$1" + local cnt="$(pdi_count "$card" "$item")" + local ret='' + + seq 1 $cnt |while read n; do case $item in + TEL) + tel="$(pdi_value "$card" "$item" "$n" |unescape)" + ttype="$(pdi_attrib "$card" "$item" "$n" TYPE)" + if [ "$tel" -a "$ttype" ]; then + printf '%s: %s\n' "$(l10n "TYPE=$ttype")" "$tel" + elif [ "$tel" ]; then + printf '%s\n' "$tel" + fi + ;; + GENDER) + gen="$(pdi_value "$card" "$item" "$n" |unescape)" + [ "$gen" ] && l10n "gender_$gen" + ;; + *) pdi_value "$card" "$item" "$n" |unescape + ;; + esac; done \ + | sed -E 's;";\\";g;' +} + +printf '%s\r\n' \ + 'Content-Type: text/csv; charset=utf-8' \ + 'Content-Disposition: inline; filename="confetti_export_'$(date +%F_%T)'.csv"' \ + '' + +printf '"%s";"%s";"%s";"%s";"%s";"%s";"%s";"%s";"%s"\n' \ + "$(l10n FN)" "$(l10n GENDER)" "$(l10n BDAY)" \ + "$(l10n TEL)" "$(l10n EMAIL)" "$(l10n ADR)" \ + "$(l10n NOTE)" "$(l10n courses)" "$(l10n CATEGORIES)" \ +| sed -E 's;­\;;;g;' + + +filter_cards \ +| order_cards \ +| while read cardfile; do + card="$(pdi_load "$cardfile")" + printf '"%s";"%s";"%s";"%s";"%s";"%s";"%s";"%s";"%s"\n' \ + "$(list_item FN)" "$(list_item GENDER)" "$(list_item BDAY)" \ + "$(list_item TEL)" "$(list_item EMAIL)" "$(list_item ADR)" \ + "$(list_item NOTE)" "$(list_attendance)" "$(list_item CATEGORIES)" +done diff --git a/cards/filter_card.sh b/cards/filter_card.sh new file mode 100755 index 0000000..aacacbb --- /dev/null +++ b/cards/filter_card.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +# Copyright 2014, 2017, 2019 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +filter="$( + seq 0 100 |while read n; do + filter_type="$(POST "filter_type${n}")" + filter_text="$(POST "filter_text${n}")" + if [ ! "$filter_type" -a ! "$filter_text" ]; then + break + elif [ "$filter_type" = CATEGORIES ]; then + printf '^CATEGORIES:' + seq 0 $(POST_COUNT filter_cat$n) |while read m; do + printf '|%s' "$(POST filter_cat$n $m)" + done + elif [ "$filter_type" = course ]; then + printf '^course:' + seq 0 $(POST_COUNT filter_course$n) |while read m; do + printf '|%s' "$(POST filter_course$n $m)" + done + else + printf '^%s:%s' "$filter_type" "$filter_text" + fi + done | sed -E \ + 's;\|+;\|;g; s;\^+;\^;g; s;:\|;:;g; + :X; s;\^[^:]*:\^;\^;g; /\^[^:]*:\^/bX; + s;^\^;;; s;\^[^:]*:$;;;' +)" + +case $(POST choice) in + filter) + REDIRECT "/cards/?o=$(POST order)&f=${filter}" + ;; + new_filter) + REDIRECT "/cards/?o=$(POST order)&f=${filter}&newfilter=yes" + ;; + export_csv) + REDIRECT "/cards/export_csv.sh?o=$(POST order)&f=${filter}" + ;; + *) + REDIRECT '/cards/' + ;; +esac diff --git a/cards/index.cgi b/cards/index.cgi new file mode 100755 index 0000000..934c19a --- /dev/null +++ b/cards/index.cgi @@ -0,0 +1,25 @@ +#!/bin/sh + +. $_EXEC/pdiread.sh +. $_EXEC/cards/l10n.sh +. $_EXEC/cards/widgets.sh +. $_EXEC/cards/list.sh + +upcase=' y;abcdefghijklmnopqrstuvwxyzäöüé;ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜÉ;; ' + +filter="$(GET f)" +order="$(GET o)" +edit="$(GET e |PATH)" + +[ "$order" ] || order=firstname +edit="${edit##*/}" + +{ w_filter_diag + printf ' + [form class="newcard" action="/cards/new_card.sh" method="POST" + [button type="submit" %s] + [input name="seed" placeholder="%s"] + ]' "$(l10n newcard)" "$(l10n vcf_seed_label)" + [ "$edit" ] && edit_card "$edit" + list_cards +} | yield_page cards #/cards/cards.css diff --git a/cards/l10n.sh b/cards/l10n.sh new file mode 100755 index 0000000..2d9dc06 --- /dev/null +++ b/cards/l10n.sh @@ -0,0 +1,63 @@ +# Copyright 2014, 2016, 2019, 2021 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +l10n(){ + local word + [ $# -eq 0 ] && read -r word || word="$*" + case $word in + newcard) printf %s "Neuen Eintrag anlegen";; + + X-HEALTH-INSURANCE) printf %s "Kran­ken­ver­sich­er­ung";; + hi_from_list) printf %s "Aus Liste";; + hi_other) printf %s "Andere";; + hi_company) printf %s "Ver­sich­er­ungs­ge­sell­schaft";; + hi_number) printf %s "Ver­sich­er­ten­num­mer";; + hi_status) printf %s "Ver­sich­er­ten­sta­tus";; + X-HEALTH-INSURANCE-NOCONTRIB) printf %s "Zu­zahl­ungs­be­frei­ung";; + X-CLIENT-REFERRAL) printf %s "Empfehl­ung durch";; + prescriptions) printf %s "Verord­nungen";; + new_prescription) printf %s "Neue Verord­nung";; + no_icd) printf %s "Kein ICD Code";; + + X-ZACK-JOINDATE) printf %s "Anmelde­datum";; + X-ZACK-LEAVEDATE) printf %s "Abmelde­datum";; + X-ZACK-JOINDATE_short) printf %s "Anm.";; + X-ZACK-LEAVEDATE_short) printf %s "Abm.";; + + *) l10n_global "$word";; + esac +} + +# BEGIN) printf %s "";; +# CALADRURI) printf %s "";; +# CALURI) printf %s "";; +# CLASS) printf %s "";; +# CLIENTPIDMAP) printf %s "";; +# END) printf %s "";; +# FBURL) printf %s "";; +# GEO) printf %s "";; +# MAILER) printf %s "";; +# NAME) printf %s "";; +# PRODID) printf %s "";; +# PROFILE) printf %s "";; +# REV) printf %s "";; +# SORT-STRING) printf %s "";; +# SOURCE) printf %s "";; +# TZ) printf %s "";; +# UID) printf %s "";; +# VERSION) printf %s "";; +# XML) printf %s "";; diff --git a/cards/list.sh b/cards/list.sh new file mode 100755 index 0000000..cd0d21f --- /dev/null +++ b/cards/list.sh @@ -0,0 +1,211 @@ +#!/bin/sh + +. "${_EXEC}"/pdiread.sh + +edit_card(){ + local cardfile="$_DATA/vcard/$1" + local tempfile card + + . $_EXEC/session_lock.sh + + if ! tempfile="$(CHECK_SLOCK "$cardfile")"; then + printf '[div .message %s]' "$(l10n "This card is not set up for editing within this session.")" + else + card="$(pdi_load "$tempfile")" + cat <<-EOF + [form .card #${cardfile##*/} action="/cards/update_card.sh" method="POST" + [input type="hidden" name="tid" value="$(transid ${tempfile})"] + [div .section .basic $( + edit_item "$card" N GENDER + [ "$(pdi_count "$card" NICKNAME)" -gt 0 ] \ + && edit_item "$card" NICKNAME + edit_item "$card" BDAY + edit_item "$card" X-ZACK-JOINDATE + [ "$(pdi_count "$card" X-ZACK-LEAVEDATE)" -gt 0 ] \ + && edit_item "$card" X-ZACK-LEAVEDATE + card_item "$card" SOUND PHOTO LOGO + )] + [div .section .phone $(edit_item "$card" TEL)] + [div .section .message $( + edit_item "$card" EMAIL + [ $(pdi_count "$card" IMPP) -gt 0 ] && edit_item "$card" IMPP + [ $(pdi_count "$card" URL ) -gt 0 ] && edit_item "$card" URL + )] + [div .section .address $(edit_item "$card" ADR)] + [div .section .note $(edit_item "$card" NOTE)] + [div .section .attendance + [h3 $(l10n course_attendance) ] $( + for course in "$_DATA"/ical/*.ics; do + printf '[label [input type="checkbox" name="attendance" value="%s" %s] %s]' \ + "${course##*/}" \ + "$(grep -qF "${course##*/} ${cardfile##*/}" "$_DATA/mappings/attendance" \ + && printf 'checked="checked"' + )" \ + "$(pdi_value "$(pdi_load "$course")" SUMMARY || l10n "(unnamed course)" |unescape |HTML)" + done) + [h3 $(l10n CATEGORIES) ] $( + grep -xE '[^ ]+' "$_DATA"/mappings/categories |while read -r cat; do + printf '[label [input type="checkbox" name="CATEGORIES" value="%s" %s] %s]' \ + "$(HTML "$cat")" \ + "$(seq 1 $(pdi_count "$card" CATEGORIES) |while read c; do + pdi_value "$card" CATEGORIES $c |grep -qxF "$cat" \ + && printf 'checked="checked"' && break + done)" \ + "$(HTML "$cat")" + done) + ] + [div .control + [div .item .delete label="$(l10n edit_delete)" + [input type="checkbox" #delete] + [label for="delete" $(l10n edit_delete)] + [button type="submit" name="action" value="delete" $(l10n edit_delete)] + ] + [div .item .newfield + [select name="newfield" + [option value="" disabled="disabled" selected="selected" $(l10n edit_addfieldtext)] + $(for f in NICKNAME EMAIL TEL IMPP ADR URL NOTE; do + printf '[option value="%s" %s] ' "$f" "$(l10n "$f")" + done) + ][button type="submit" name="action" value="addfield" $(l10n edit_addfield)] + ] + [button .item type="submit" name="action" value="update" $(l10n edit_update)] + [button .item type="submit" name="action" value="cancel" $(l10n edit_cancel)] + ] + [input type="hidden" name="UID" value="$(pdi_value "$card" UID |HTML)"] + [input type="hidden" name="card" value="${cardfile##*/}"] + ] + EOF + fi +} + +print_card(){ + local cardfile="$1" + local card="$(pdi_load "$cardfile")" + cat <<-EOF + [div .card #${cardfile##*/} + [div .section .basic . $( + card_item "$card" FN GENDER NICKNAME BDAY X-ZACK-JOINDATE X-ZACK-LEAVEDATE SOUND PHOTO LOGO + )] + [div .section .phone . $(card_item "$card" TEL)] + [div .section .message . $(card_item "$card" EMAIL IMPP URL)] + [div .section .address . $(card_item "$card" ADR)] + [div .section .note . $(card_item "$card" NOTE)] + [div .section .attendance [h3 $(l10n course_attendance) ] [ul + $(grep -F " ${cardfile##*/}" "$_DATA/mappings/attendance" |while read each discard; do + printf '[li [a .item .attendance href="/courses#%s" . %s]]' \ + "$each" \ + "$(pdi_value "$(pdi_load "$_DATA/ical/$each")" SUMMARY || l10n "(unnamed course)" |unescape |HTML)" + done)] + $(card_item "$card" CATEGORIES) + ] + [div .control + [a .item href="/cards/edit_card.sh?card=${cardfile##*/}" $(l10n edit)] + [a .item href="/cards/export_card.sh?card=${cardfile##*/}" $(l10n vcf_export)] + ] + ] + EOF +} + +print_cards(){ + local cardfile cachefile date size name ldate=0 lsize lname + + while read cardfile; do + cachefile="${_DATA}/cache/${cardfile##*/}.cache" + # if [ -s "$cachefile" -a "$cachefile" -nt "$cardfile" \ + # -a "$cachefile" -nt "${_EXEC}/cards" ]; then + if [ -s "$cachefile" -a "$cachefile" -nt "$cardfile" ]; then + cat "$cachefile" + else + print_card "$cardfile" |tee "$cachefile" + fi + done +} + +filter_attendance(){ + fatt="$1" + attfile="$_DATA/mappings/attendance" + + if [ ! "$fatt" ]; then + # debug 'list all' + printf '%s\n' "$_DATA/vcard"/*.vcf + elif [ "${fatt#* }" = "${fatt}" ]; then + # debug "list $fatt" + grep -xiE "(${fatt}) .+vcf" "$attfile" \ + | while read vcf; do + printf '%s/vcard/%s\n' "$_DATA" "${vcf##* }" + done + else + # debug "filter ${fatt%% *}" + filter_attendance "${fatt#* }" \ + | while read vcf; do + grep -xiE "(${fatt%% *}) ${vcf##*/}" "$attfile" + done \ + | while read vcf; do + printf '%s/vcard/%s\n' "$_DATA" "${vcf##* }" + done + fi +} + +filter_cards(){ + local filter f fex='x;p;' + + filter="$(printf %s "${filter}" \ + | sed -E 's;[]\/\(\)\\\$\?\.\+\*\;\[\{\}];\\&;g; + '"$upcase" + )^" + + while [ "$filter" ]; do + f="${filter%%^*}" filter="${filter#*^}" + case $f in + '') break + ;; + COURSE:*) fatt="${fatt}${fatt:+ }${f#*:}" + ;; + ANY:*) fex="/\n.*(\;[^:]*)?:[^\n]*(${f#*:})[^\n]*\r?\n/{${fex}}" + ;; + NAME:*) fex="/\n(N|FN|NICKNAME)(\;[^:]*)?:[^\n]*(${f#*:})[^\n]*\r?\n/{${fex}}" + ;; + STREET:*|ZIP:*) fex="/\nADR(\;[^:]*)?:[^\n]*(${f#*:})[^\n]*\r?\n/{${fex}}" + ;; + *) fex="/\n${f%%:*}(\;[^:]*)?:[^\n]*(${f#*:})[^\n]*\r?\n/{${fex}}" + ;; + esac + done + + # for cardfile in "${_DATA}"/vcard/*.vcf; do + filter_attendance "$fatt" |while read cardfile; do + printf '%s\n' "$cardfile" + cat "$cardfile" + done \ + | sed -nE ':X; /\nEND\;?:VCARD\r?$/!{ N; bX; }; h; s;\n.*$;;; x; s;^[^\n]+\n;;; + '"$upcase""$fex" +} + +order_cards() { + local cardfile card + + while read cardfile; do + card="$(pdi_load "$cardfile")" + + case $order in + firstname) + printf '%s %s\n' "$(pdi_value "$card" FN)" "$cardfile" + ;; + lastname) + printf '%s %s\n' "$(pdi_value "$card" N || pdi_value "$card" FN)" "$cardfile" + ;; + bdate) + printf '%s %s\n' "$(pdi_value "$card" BDAY || printf 0000-00-00)" "$cardfile" + ;; + esac + done \ + | sort \ + | sed -E 's;^.*\t;;g' +} + +list_cards(){ + filter_cards \ + | order_cards \ + | grep -xvF "$edit" \ + | print_cards +} diff --git a/cards/new_card.sh b/cards/new_card.sh new file mode 100755 index 0000000..0273a2c --- /dev/null +++ b/cards/new_card.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +# Copyright 2014, 2019 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +filter="$(REF f)" +order="$(REF o)" + +uid="$(timeid)$(randomid)" # 32 Octets UID, starting with timestamp +card="${uid}.vcf" + +vcf_escape(){ + for each in "$@"; do + printf %s\\n "$each" \ + | sed -E ':X;$!{N;bX}; s;\r\n;\n;g; s;([;,\\]);\\\1;g; s;\n;\\n;g;' + done \ + | sed -E ':X;$!{N;bX}; s;\n;\;;g' +} + +IFS='|' read -r date fn ln bmonth byear tel tcell junk1 email junk2 note <<-EOF + $(POST seed |tr \\t \|) + EOF + +[ ${#byear} = 1 ] && byear="200$byear" +[ ${#byear} = 2 ] && byear="20$byear" +[ ${#bmonth} = 1 ] && bmonth="0$bmonth" + +mn="" +case $fn in + *\ *) + mn="${fn#* }" + fn="${fn%% *}" + ;; +esac + +mkdir -p "${_DATA}/lock/vcard/" +lockdir="${_DATA}/lock/vcard/${card}/" +lockfile=${lockdir}/${SESSION_ID} + +if mkdir "$lockdir"; then + cat >"$lockfile" <<-EOF + BEGIN:VCARD + VERSION:4.0 + N:$(vcf_escape "$ln" "$fn" "$mn" "" "") + FN:$(vcf_escape "${fn}${mn:+ }${mn} ${ln}") + BDAY:$(parse_date "${byear}-${bmonth}-01") + TEL:$(vcf_escape "$tel") + TEL;TYPE=CELL:$(vcf_escape "$tcell") + EMAIL:$(vcf_escape "$email") + X-ZACK-JOINDATE:$(parse_date "$date") + ADR: + NOTE:$(vcf_escape "$note") + UID:${uid} + END:VCARD + EOF + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" +else + SET_COOKIE session message="EDITLOCK" + REDIRECT "/cards/?o=${order}&f=${filter}" +fi diff --git a/cards/update_card.sh b/cards/update_card.sh new file mode 100755 index 0000000..d942e9a --- /dev/null +++ b/cards/update_card.sh @@ -0,0 +1,151 @@ +#!/bin/sh + +# Copyright 2014, 2016, 2019, 2020, 2021 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +. "$_EXEC/pdiread.sh" +. "$_EXEC/session_lock.sh" +. "$_EXEC/cgilite/storage.sh" + +unset filter order card action newfield +unset cardfile attfile tempfile +unset vcf field cnt delete_key + +filter="$(REF f)" +order="$(REF o)" + +card="$(POST card |PATH)"; card="${card##*/}" +cardfile="$_DATA/vcard/${card}" +attfile="$_DATA/mappings/attendance" + +action="$(POST action)" +newfield="$(POST newfield |grep -m 1 -xE '[A-Z][A-Z0-9-]*')" + +if printf '%s\n' "$action" |grep -qxE 'addfield [A-Z][A-Z0-9]*'; then + newfield="${action##* }" + action=addfield +fi + +if ! tempfile=$(CHECK_SLOCK "$cardfile"); then + SET_COOKIE 0 message="NO VALID FILE LOCK" + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" + exit 0 +elif [ "$(POST tid)" != "$(transid "$tempfile")" ]; then + SET_COOKIE 0 message="INVALID TRANSACTION ID" + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" + exit 0 +fi + +vcf_escape(){ + for each in "$@"; do + printf %s\\n "$each" \ + | sed -E ':X;$!{N;bX}; s;\r\n;\n;g; s;([;,\\]);\\\1;g; s;\n;\\n;g;' + done \ + | sed -E ':X;$!{N;bX}; s;\n;\;;g' +} + +# [ "${_POST[hi_select]}" = "list" ] || _POST[hi_company]="${_POST[hi_other]}" +# [ -n "${_POST[hi_company]}${_POST[hi_number]}${_POST[hi_status]}" ] \ +# && _POST[X-HEALTH-INSURANCE]="$(vcf_escape "${_POST[hi_company]}" "${_POST[hi_number]}" "${_POST[hi_status]}")" + +vcf="$(pdi_load "$tempfile")" + +n1="$(POST 1N)" n2="$(POST 2N)" n3="$(POST 3N)" n4="$(POST 4N)" n5="$(POST 5N)" +# 3N (Middle Names) is not actually used +n3="${n2#${n2%% *}}" + +vcf="$(pdi_update_value "$vcf" N 1 "$(vcf_escape "$n1" "${n2%% *}" "${n3# }" "$n4" "$n5")")" +vcf="$(pdi_update_value "$vcf" FN 1 "$(vcf_escape "$n4 $n2 $n1 $n5" |sed -E 's;(^ +| +$);;g; s; +; ;g;')")" +vcf="$(printf '%s\n' "$vcf" |sed -E "/^CATEGORIES;[^:]*:.*$/d")" + +for field in $(POST_KEYS |grep -xE '[A-Z][A-Z0-9-]*'); do + for cnt in $(seq 1 $(POST_COUNT "$field")); do + case "$field" in + # (TEL) + # printf '%s;TYPE=%s:%s\r\n' "${field}" "${_POST[phonetype${key#TEL}]}" "$(vcf_escape "$(POST "$field" "$cnt")")" + # ;; + TEL) + vcf="$(pdi_update_attrib "$vcf" TEL $cnt TYPE="$(POST teltype $cnt |grep -Exm1 'HOME|WORK|CELL|FAX')")" + vcf="$(pdi_update_value "$vcf" "$field" "$cnt" "$(vcf_escape "$(POST "$field" "$cnt")")")" + ;; + *) + vcf="$(pdi_update_value "$vcf" "$field" "$cnt" "$(vcf_escape "$(POST "$field" "$cnt")")")" + ;; + esac +done; done + +# delete fields, first mark for deletion using delete_key +# this way the field enumeration is preserved during the process +# finally filter marked lines +delete_key="$(randomid)" +for delete in $(POST_KEYS |grep -xE '[A-Z][A-Z0-9-]*_delete_[0-9]+'); do + f="${delete%%_*}"; c="${delete##*_}"; + [ "$(POST "$delete")" = "true" ] && vcf="$(pdi_update_value "$vcf" "$f" "$c" "delete=${delete_key}")" +done +vcf="$(printf '%s\n' "$vcf" |sed -E "/^[^:]+:delete=${delete_key}\$/d")" + +if [ "$action" = addfield ]; then + vcf="$(pdi_update_value "$vcf" "$newfield" $(( $(pdi_count "$vcf" "$newfield") + 1 )) '')" +fi +printf '%s' "$vcf" |grep -vx '' >"$tempfile" + +case "$action" in + addfield) + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" + ;; + update) + if LOCK "$attfile"; then + grep -F " ${card}" "$attfile" |while read course junk; do + touch "$_DATA/ical/${course}" + done + sed -i -E "/^.+ ${card}\$/d" "$attfile" + seq 1 $(POST_COUNT attendance) |while read n; do + printf '%s %s\n' "$(POST attendance $n)" "$card" + done >>"$attfile" + grep -F " ${card}" "$attfile" |while read course junk; do + touch "$_DATA/ical/${course}" + done + RELEASE "$attfile" + else + SET_COOKIE 0 message="COULD NOT UPDATE COURSE MAPPINGS" + fi + + cp "$tempfile" "$cardfile" + RELEASE_SLOCK "$cardfile" + REDIRECT "/cards/?o=${order}&f=${filter}#${card}" + ;; + cancel) + RELEASE_SLOCK "$cardfile" + [ -f "$cardfile" ] \ + && REDIRECT "/cards/?o=${order}&f=${filter}#${card}" \ + || REDIRECT "/cards/?o=${order}&f=${filter}" + ;; + delete) + rm "$cardfile" + RELEASE_SLOCK "$cardfile" + if LOCK "$attfile"; then + grep -F " ${card}" "$attfile" |while read course junk; do + touch "$_DATA/ical/${course}" + done + sed -i -E "/^.+ ${card}\$/d" "$attfile" + RELEASE "$attfile" + else + SET_COOKIE 0 message="COULD NOT UPDATE COURSE MAPPINGS" + fi + REDIRECT "/cards/?o=${order}&f=${filter}" + ;; +esac diff --git a/cards/widgets.sh b/cards/widgets.sh new file mode 100755 index 0000000..c0c0594 --- /dev/null +++ b/cards/widgets.sh @@ -0,0 +1,277 @@ +# Copyright 2014 - 2019, 2021 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +list_categories() { + grep -vxE '^[ ]*$' "${_DATA}/mappings/categories" +} + +list_courses() { + local file name cachefile="${_DATA}/cache/courses.ui.cache" + if [ $cachefile -nt "${_DATA}/ical" ]; then + cat "$cachefile" + else + for file in "$_DATA/ical"/*.ics; do + name="$(pdi_value "$(pdi_load "$file")" SUMMARY |HTML)" + printf '%s %s\n' "$file" "$name" + done \ + | sort -k2 |tee "$cachefile" + fi +} + +w_filter_item() { +n=$3 +cat <%s' \ + "$item" "$item" "$(pdi_value "$card" "$item" $c |unescape |HTML)" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + TEL) printf '[h3 %s]' "$(l10n "$item")" + seq 1 $cnt |while read c; do + printf '[checkbox "%s_delete_%i" "true" .delete #%s_delete_%i][label for="%s_delete_%i" %s]' \ + "$item" $c "$item" $c "$item" $c "$(l10n delete)" + teltype="$(pdi_attrib "$card" TEL $c TYPE)" + printf '[select .item .teltype name="teltype" + [option value="" disabled="disabled" %s %s] + [option value="HOME" %s %s] + [option value="WORK" %s %s] + [option value="CELL" %s %s] + [option value="FAX" %s %s] + ]\n' \ + "$([ "$teltype" = '' ] && printf 'selected="selected"')" "$(l10n teltype)" \ + "$([ "$teltype" = 'HOME' ] && printf 'selected="selected"')" "$(l10n TYPE=HOME)" \ + "$([ "$teltype" = 'WORK' ] && printf 'selected="selected"')" "$(l10n TYPE=WORK)" \ + "$([ "$teltype" = 'CELL' ] && printf 'selected="selected"')" "$(l10n TYPE=CELL)" \ + "$([ "$teltype" = 'FAX' ] && printf 'selected="selected"')" "$(l10n TYPE=FAX)" + + printf '[input .item .%s name="%s" value="%s" placeholder="%s"]' \ + "$item" "$item" "$(pdi_value "$card" "$item" $c |unescape |HTML)" "$(l10n "$item")" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + *)printf '[h3 %s]' "$(l10n "$item")" + seq 1 $cnt |while read c; do + printf '[checkbox "%s_delete_%i" "true" .delete #%s_delete_%i][label for="%s_delete_%i" %s]' \ + "$item" $c "$item" $c "$item" $c "$(l10n delete)" + printf '[input .item .%s name="%s" value="%s" placeholder="%s"]' \ + "$item" "$item" "$(pdi_value "$card" "$item" $c |unescape |HTML)" "$(l10n "$item")" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + esac + done +} diff --git a/actions/edit_categories.sh b/categories/edit_categories.sh similarity index 72% rename from actions/edit_categories.sh rename to categories/edit_categories.sh index 2c34d26..7ee6f36 100755 --- a/actions/edit_categories.sh +++ b/categories/edit_categories.sh @@ -19,13 +19,17 @@ catfile="${_DATA}/mappings/categories" -remove="${_POST[remove]}" -newcat="${_POST[newcat]}" +remove="$(POST remove)" +newcat="$(POST newcat)" -if [ "${_POST[add]}" = "add" ]; then - printf %s\\n "$newcat" >>"$catfile" -elif [ -n "$remove" ]; then - sed -ri '/^'"${remove}"'$/d' $catfile +if [ "$(POST add)" = "add" ]; then + categories="$( { + cat "$catfile" + printf %s\\n "$newcat" + } |sort -u )" + printf %s\\n "$categories" >"$catfile" +elif [ "$remove" ]; then + sed -E -i '/^'"${remove}"'$/d' "$catfile" fi -redirect "?p=categories" +REDIRECT "/categories/" diff --git a/categories/index.cgi b/categories/index.cgi new file mode 100755 index 0000000..236b1e3 --- /dev/null +++ b/categories/index.cgi @@ -0,0 +1,71 @@ +#!/bin/sh +# Copyright 2015, 2017, 2018, 2021 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +. $_EXEC/pdiread.sh +. $_EXEC/categories/l10n.sh + +catfile="${_DATA}/mappings/categories" + +list_categories() { + grep -vxE '[ ]*' "$catfile" |sort -u +} + +list_catsel(){ + local vcf="$1" card="$2" n=1 cats="${BR}" + while cats="${cats}${BR}$(pdi_value "$vcf" CATEGORIES $n)"; do n=$((n + 1)); done + + list_categories |while read cat; do + printf '[li [label [input %s type="checkbox" name="%s" value="%s"] %s]]' \ + "$([ "${cats%*${BR}${cat}${BR}*}" != "$cats" ] && printf checked=checked)" \ + "$(HTML "$card")" "$(HTML "$cat")" "$(HTML "$cat")" + done +} + +cat <. -echo -n "Content-Type: text/calendar;charset=utf-8\n\n" -course="${_GET[course]}" -cat "$_DATA/ical/$course" +l10n(){ + local word + [ $# -eq 0 ] && read -r word || word="$*" + case $word in + cat_remove) printf %s "-";; + cat_add) printf %s "+";; + cat_newlabel) printf %s "neue Kategorie";; + cat_update) printf %s "Zuweisungen übernehmen";; + categories_label) printf %s "Kategorien";; + + *) l10n_global "$word";; + esac +} diff --git a/actions/update_categories.sh b/categories/update_categories.sh similarity index 54% rename from actions/update_categories.sh rename to categories/update_categories.sh index c3b2e4c..108c5d5 100755 --- a/actions/update_categories.sh +++ b/categories/update_categories.sh @@ -1,6 +1,6 @@ -#!/bin/zsh +#!/bin/sh -# Copyright 2016 Paul Hänsch +# Copyright 2016, 2021 Paul Hänsch # # This file is part of Confetti. # @@ -17,35 +17,25 @@ # You should have received a copy of the GNU Affero General Public License # along with Confetti. If not, see . -. "$_EXEC"/pages/categories.sh +. "$_EXEC"/cgilite/storage.sh +. "$_EXEC"/pdiread.sh catfile="${_DATA}/mappings/categories" -set_categories(){ - card="${_DATA}/vcard/$1" - cats="$2" - cardcats="$(sed -nr 's;^CATEGORIES(\;[^":]+|\;"[^"]+")*:([^\r]+)\r?$;\2;p' "$card")" +for card in "${_DATA}"/vcard/*.vcf; do + n='' postcats='' cardcats='' + vcf="$(pdi_load "$card")" - debug "CARD: $card" - debug "CATS: $cardcats" - debug "NEW: $cats" - if [ "$cats" != "$cardcats" ]; then - sed -ri ' + n=1; while postcats="${postcats}${postcats:+,}$(POST "${card##*/}" $n)"; do n=$((n+1)); done + n=1; while cardcats="${cardcats}${cardcats:+,}$(pdi_value "$vcf" CATEGORIES $n)"; do n=$((n+1)); done + + if [ "${postcats}" != "${cardcats}" ] && LOCK "$card"; then + sed -E -i ' /^CATEGORIES[;:]/d - /^END:VCARD *\r?$/iCATEGORIES:'"$cats"'\r + /^END;?:VCARD *\r?$/iCATEGORIES:'"${postcats%,}"'\r ' "${card}" + RELEASE "$card" fi -} - -listcards |while read card; do - cardcats='' - for n in "$card" "$card"{0..100}; do - if [ -z "${_POST[$n]}" ]; then - set_categories "$card" "$cardcats" - break - fi - cardcats="${cardcats}${cardcats:+,}${_POST[$n]}" - done done -redirect "?p=categories" +REDIRECT /categories/ diff --git a/cgilite/cgilite.sh b/cgilite/cgilite.sh new file mode 100755 index 0000000..f766ee2 --- /dev/null +++ b/cgilite/cgilite.sh @@ -0,0 +1,268 @@ +#!/bin/sh + +# Copyright 2017 - 2020 Paul Hänsch +# +# This is CGIlite. +# A collection of posix shell functions for writing CGI scripts. +# +# CGIlite is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# CGIlite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with CGIlite. If not, see . + +[ -n "$include_cgilite" ] && return 0 +# guard set after webserver part + +# ksh and zsh workaround +# set -o posix # ksh, not portable +setopt -o OCTAL_ZEROES 2>&- + +CR=" " +BR=' +' +cgilite_timeout=2 + +PATH(){ + local str seg out + [ $# -eq 0 ] && str="$(cat)" || str="$*" + while [ "$str" ]; do + seg=${str%%/*}; str="${str#*/}" + case $seg in + ..) out="${out%/}"; out="${out%/*}/";; + .|'') out="${out%/}/";; + *) out="${out%/}/${seg}";; + esac; + [ "$seg" = "$str" ] && break + done + [ "${str}" -a "${out}" ] && printf %s "$out" || printf %s/ "${out%/}" +} + +HEX_DECODE=' + s;\\;\\\\;g; :HEXDECODE_X; s;%([^0-9A-F]);\\045\1;g; tHEXDECODE_X; + # Hexadecimal { %00 - %FF } will be transformed to octal { \000 - \377 } for posix printf + s;%[0123].;&\\0;g; s;%[4567].;&\\1;g; s;%[89AB].;&\\2;g; s;%[CDEF].;&\\3;g; + s;%[048C][0-7]\\.;&0;g; s;%[048C][89A-F]\\.;&1;g; s;%[159D][0-7]\\.;&2;g; s;%[159D][89A-F]\\.;&3;g; + s;%[26AE][0-7]\\.;&4;g; s;%[26AE][89A-F]\\.;&5;g; s;%[37BF][0-7]\\.;&6;g; s;%[37BF][89A-F]\\.;&7;g; + s;%.[08](\\..);\10;g; s;%.[19](\\..);\11;g; s;%.[2A](\\..);\12;g; s;%.[3B](\\..);\13;g; + s;%.[4C](\\..);\14;g; s;%.[5D](\\..);\15;g; s;%.[6E](\\..);\16;g; s;%.[7F](\\..);\17;g; +' + +HEX_DECODE(){ + printf -- "$(printf %s "$1" |sed -E "$HEX_DECODE")" +} + +if [ -z "$REQUEST_METHOD" ]; then + # no webserver variables means we are running via inetd / ncat + # so use builtin web server + + # Use env from inetd as webserver variables + REMOTE_ADDR="${TCPREMOTEIP}" + SERVER_NAME="${TCPLOCALIP}" + SERVER_PORT="${TCPLOCALPORT}" + + # Wait 2 seconds for request or kill connection through watchdog. + # Once Request is received the watchdog will be suspended (killed). + # At the end of the loop the watchdog will be restarted to enable + # timeout for the subsequent request. + + (sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$! + while read REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL; do + [ "${SERVER_PROTOCOL#HTTP/1.[01]${CR}}" ] && break + kill $cgilite_watchdog + + SERVER_PROTOCOL="${SERVER_PROTOCOL%${CR}}" + PATH_INFO="$(HEX_DECODE "${REQUEST_URI%\?*}" |PATH)" + [ "${REQUEST_URI}" = "${REQUEST_URI#*\?}" ] \ + && QUERY_STRING='' \ + || QUERY_STRING="${REQUEST_URI#*\?}" + cgilite_headers=''; while read -r hl; do + hl="${hl%${CR}}"; [ "$hl" ] || break + case $hl in + 'Content-Length: '*) CONTENT_LENGTH="${hl#*: }";; + 'Content-Type: '*) CONTENT_TYPE="${hl#*: }";; + esac + cgilite_headers="${cgilite_headers}${hl}${BR}" + done + + export REMOTE_ADDR SERVER_NAME SERVER_PORT REQUEST_METHOD REQUEST_URI SERVER_PROTOCOL \ + PATH_INFO QUERY_STRING CONTENT_TYPE CONTENT_LENGTH + + # Try to serve multiple requests, provided that script serves a + # Content-Length header. + # Without Content-Length header, connection will terminate after + # script. + + cgilite_status='200 OK'; cgilite_response=''; cgilite_cl="Connection: close${CR}${BR}"; + . "$0" | while read -r l; do case $l in + Status:*) + cgilite_status="${l#Status: }";; + Content-Length:*) + cgilite_cl="" + cgilite_response="${cgilite_response:+${cgilite_response}${BR}}${l}";; + Connection:*) + cgilite_cl="${l}${BR}";; + $CR) printf '%s %s\r\n%s%s\r\n' \ + 'HTTP/1.1' "${cgilite_status%${CR}}" \ + "${cgilite_response}${cgilite_response:+${BR}}" "${cgilite_cl}" + cat || kill $$ + [ "${cgilite_cl#Connection}" = "${cgilite_cl}" ]; exit;; + *) cgilite_response="${cgilite_response:+${cgilite_response}${BR}}${l}";; + esac; done || exit 0; + (sleep $cgilite_timeout && kill $$) & cgilite_watchdog=$! + done + kill $cgilite_watchdog + exit 0 +fi + +include_cgilite="$0" + +if [ "${REQUEST_METHOD}" = POST -a "${CONTENT_LENGTH:-0}" -gt 0 -a \ + "${CONTENT_TYPE}" = "application/x-www-form-urlencoded" ]; then + cgilite_post="$(head -c "$CONTENT_LENGTH")" +fi + +debug(){ [ $# -gt 0 ] && printf '%s\n' "$@" >&2 || tee -a /dev/stderr; } +[ "${DEBUG+x}" ] && env >&2 + +cgilite_count(){ + printf %s "&$1" \ + | grep -oE '&'"$2"'=[^&]*' \ + | wc -l +} + +cgilite_value(){ + local str="&$1" name="$2" cnt="${3:-1}" + while [ $cnt -gt 0 ]; do + [ "${str}" = "${str#*&${name}=}" ] && return 1 + str="${str#*&${name}=}" + cnt=$((cnt - 1)) + done + printf -- "$(printf %s "${str%%&*}" |sed -E 's;\+; ;g;'"$HEX_DECODE")" +} + +cgilite_keys(){ + local str="&$1" + while [ "${str#*&}" != "${str}" ]; do + str="${str#*&}" + printf '%s\n' "${str%%=*}" + done \ + | sort -u +} + +GET(){ cgilite_value "${QUERY_STRING}" $@; } +GET_COUNT(){ cgilite_count "${QUERY_STRING}" $1; } +GET_KEYS(){ cgilite_keys "${QUERY_STRING}"; } + +POST(){ cgilite_value "${cgilite_post}" $@; } +POST_COUNT(){ cgilite_count "${cgilite_post}" $1; } +POST_KEYS(){ cgilite_keys "${cgilite_post}"; } + +REF(){ cgilite_value "${HTTP_REFERER#*\?}" $@; } +REF_COUNT(){ cgilite_count "${HTTP_REFERER#*\?}" $1; } +REF_KEYS(){ cgilite_keys "${HTTP_REFERER#*\?}"; } + +HEADER(){ + # Read value of header line. Use this instead of + # referencing HTTP_* environment variables. + if [ -n "${cgilite_headers+x}" ]; then + local str="${BR}${cgilite_headers}" + [ "${str}" = "${str#*${BR}${1}: }" ] && return 1 + str="${str#*${BR}${1}: }" + printf %s "${str%%${BR}*}" + else + local var="HTTP_$(printf %s "$1" |tr a-z- A-Z-)" + eval "[ \"\$$var\" ] && printf %s \"\$$var\" || return 1" + # eval "printf %s \"\$HTTP_$(printf %s "${1}" |tr a-z A-Z |tr -c A-Z _)\"" + fi +} + +COOKIE(){ + HEX_DECODE "$( + HEADER Cookie \ + | grep -oE '(^|; ?)'"$1"'=[^;]*' \ + | sed -En "${2:-1}"'{s;^[^=]+=;;; s;\+; ;g; p;}' + )" +} + +HTML(){ + # Escape HTML cahracters + # Also escape [, ], and \n for use in html-sh + local str out + [ $# -eq 0 ] && str="$(cat)" || str="$*" + while [ "$str" ]; do + case $str in + \&*) out="${out}&";; + \<*) out="${out}<";; + \>*) out="${out}>";; + \"*) out="${out}"";; + \'*) out="${out}'";; + \[*) out="${out}[";; + \]*) out="${out}]";; + "${CR}"*) out="${out} ";; + "${BR}"*) out="${out} ";; + *) out="${out}${str%"${str#?}"}";; + esac + str="${str#?}" + done + printf %s "$out" +} + +URL(){ + # Escape pathes, so they can be used in link tags and HTTP Headers + local str out + [ $# -eq 0 ] && str="$(cat)" || str="$*" + while [ "$str" ]; do + case $str in + \&*) out="${out}%26";; + \"*) out="${out}%22";; + \'*) out="${out}%27";; + \?*) out="${out}%3F";; + \#*) out="${out}%23";; + \[*) out="${out}%5B";; + \]*) out="${out}%5D";; + \ *) out="${out}%20";; + " "*) out="${out}%09";; + "${CR}"*) out="${out}%0D";; + "${BR}"*) out="${out}%0A";; + %*) out="${out}%25";; + *) out="${out}${str%"${str#?}"}";; + esac + str="${str#?}" + done + printf %s "$out" +} + +SET_COOKIE(){ + # Param: session | +seconds | [date] + # Param: name=value + # Param: Path= | Domain= | Secure + local expire cookie + case "$1" in + ''|0|session) expire='';; + [+-][0-9]*) expire="$(date -R -d @$(($(date +%s) + $1)))";; + *) expire="$(date -R -d "$1")";; + esac + cookie="$2" + + printf 'Set-Cookie: %s; HttpOnly; SameSite=Lax' "$cookie" + [ -n "$expire" ] && printf '; Expires=%s' "${expire%+????}${expire:+GMT}" + [ $# -ge 3 ] && shift 2 && printf '; %s' "$@" + printf '\r\n' +} + +REDIRECT(){ + printf '%s: %s\r\n' \ + Status "303 See Other" \ + Content-Length 0 \ + Location "$*" + printf '\r\n' + exit 0 +} diff --git a/cgilite/file.sh b/cgilite/file.sh new file mode 100755 index 0000000..04a8ef6 --- /dev/null +++ b/cgilite/file.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +# Copyright 2016 - 2019 Paul Hänsch +# +# This file is part of cgilite. +# +# cgilite is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# cgilite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with cgilite. If not, see . + +[ -n "$include_fileserve" ] && return 0 +include_fileserve="$0" + +file_type(){ + case ${1##*.} in + html|html) printf 'text/html';; + css) printf 'text/css';; + js) printf 'text/javascript';; + txt) printf 'text/plain';; + sh) printf 'text/shellscript';; + jpg|jpeg) printf 'image/jpeg';; + png) printf 'image/png';; + svg) printf 'image/svg+xml';; + gif) printf 'image/gif';; + webm) printf 'video/webm';; + mp4|m4v) printf 'video/mp4';; + m4a) printf 'audio/mp4';; + ogg) printf 'audio/ogg';; + xml) printf 'application/xml';; + m3u8) printf 'application/x-mpegURL';; + ts) printf 'video/MP2T';; + mpd) printf 'application/dash+xml';; + m4s) printf 'video/iso.segment';; + *) printf 'application/octet-stream';; + esac +} + +FILE(){ + local file file_size file_date http_date cachedate range mime + file="$1" mime="$2" + + if ! [ -f "$file" ]; then + printf 'Content-Length: 0\r\nStatus: 404 Not Found\r\n\r\n' + exit 0 + elif ! [ -r "$file" ]; then + printf 'Content-Length: 0\r\nStatus: 403 Forbidden\r\n\r\n' + exit 0 + fi + + file_size="$(stat -Lc %s "$file")" + file_date="$(stat -Lc %Y "$file")" + http_date="$(date -uRd @$file_date)" + http_date="${http_date%+0000}GMT" + cachedate="$( + # Parse the allowable date formats from Section 3.3.1 of + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html + HEADER If-Modified-Since \ + | sed -E 's;^[^ ]+, ([0-9]{2}) (...) ([0-9]{4}) (..:..:..) GMT$;\3-\2-\1 \4;; + s;^[^ ]+, ([0-9]{2})-(...)-([789][0-9]) (..:..:..) GMT$;19\3-\2-\1 \4;; + s;^[^ ]+, ([0-9]{2})-(...)-([0-6][0-9]) (..:..:..) GMT$;20\3-\2-\1 \4;; + s;^[^ ]+ (...) ([0-9]{2}) (..:..:..) ([0-9]{4})$;\4-\1-\2 \3;; + s;^[^ ]+ (...) ([0-9]) (..:..:..) ([0-9]{4})$;\4-\1-\2 \3;; + s;Jan;01;; s;Feb;02;; s;Mar;03;; s;Apr;04;; s;May;05;; s;Jun;06;; + s;Jul;07;; s;Aug;08;; s;Sep;09;; s;Oct;10;; s;Nov;11;; s;Dec;12;;' \ + | xargs -r0 date +%s -ud 2>&- + )" + + range="$(HEADER Range |sed -nE 's;^bytes=([0-9]+-[0-9]*|-[0-9]+)$;\1;p;q;')" + case "$range" in + *-) range="${range}$((file_size - 1))";; + -*) [ ${range#-} -le $file_size ] \ + && range="$((file_size - ${range#-}))-$((file_size - 1))" \ + || range="0-$((file_size - 1))";; + *-*) [ ${range#*-} -ge $file_size ] \ + && range="${range%-*}-$((file_size - 1))";; + esac + + if [ "$file_date" -lt "$cachedate" ] 2>&-; then + printf '%s: %s\r\n' \ + Status '304 Not Modified' \ + Content-Length 0 \ + Last-Modified "$http_date" + printf '\r\n' + + elif [ -z "$range" ]; then + printf '%s: %s\r\n' \ + Status "200 OK" \ + Accept-Ranges bytes \ + Last-Modified "$http_date" \ + Content-Type "${mime:-$(file_type "$file")}" \ + Content-Length $file_size + printf '\r\n' + + [ "$REQUEST_METHOD" != HEAD ] && cat "$file" + + elif [ "${range%-*}" -le "${range#*-}" ]; then + printf '%s: %s\r\n' \ + Status "206 Partial Content" \ + Accept-Ranges bytes \ + Last-Modified "$http_date" \ + Content-Type "${mime:-$(file_type "$file")}" \ + Content-Range "bytes ${range}/${file_size}" \ + Content-Length "$((${range#*-} - ${range%-*} + 1))" + printf '\r\n' + + [ "$REQUEST_METHOD" != HEAD ] \ + && tail -c+$((${range%-*} + 1)) "$file" \ + | head -c "$((${range#*-} - ${range%-*} + 1))" + + elif [ "${range%-*}" -gt "${range#*-}" ]; then + printf '%s: %s\r\n' \ + Status "216 Range Not Satisfiable" \ + Content-Length 0 \ + Content-Range \*/${file_size} + printf '\r\n' + fi +} diff --git a/cgilite/html-sh.sed b/cgilite/html-sh.sed new file mode 100755 index 0000000..8d7b61c --- /dev/null +++ b/cgilite/html-sh.sed @@ -0,0 +1,69 @@ +#!/bin/sed -nEf + +:Escapes +s,\\\\,\\,g; s,\\&,\&,g; +s,\\<,\<,g; s,\\>,\>,g; +s,\\",\",g; s,\\',\',g; +s,\\\[,\[,g; s,\\\],\],g; +s,\\\.,\.,g; s,\\#,\#,g; +s,\\,,g; + +:CommentHandle +x; /^<\/!-->/{ + x; /--]/{ + H; s;^(.*)--].*$;\1-->;p; + g; s;^.*--]([^\n]*)$;\1; + x; s;^\n(.*)\n[^\n]*$;\1;; x; + bCommentEnd + } + p; b; +} +x; +:CommentEnd + +:shortcuts +s;\[hidden[ \t]+"([^"]*)"[ \t]+"([^"]*)";[input type="hidden" name="\1" value="\2";g; +s;\[checkbox[ \t]+"([^"]*)"[ \t]+"([^"]*)";[input type="checkbox" name="\1" value="\2";g; +s;\[radio[ \t]+"([^"]*)"[ \t]+"([^"]*)";[input type="radio" name="\1" value="\2";g; +s;\[submit[ \t]+"([^"]*)"[ \t]+"([^"]*)";[button type="submit" name="\1" value="\2";g; +s;\[a[ \t]+"([^"]*)";[a href="\1";g; +s;\[img[ \t]+"([^"]*)"[ \t]+"([^"]*)";[img src="\1" alt="\2";g; + +s;\[!([^]\[]*)\];;g; +s;\[!--([^]\[]*)--\];;g; + +:tags +s;\[([^]\[< \t]+)([^]\[]*)\];<\1>\2;g; +t tags; + +G; +:tagclose +s;^([^]\n]*)\]([^\n]*)\n([^\n]+);\1\3\2; +t tagclose; +h; s;^([^\n]*)\n;;; x; s;\n.*$;;; + +:tagopen +s;^([^\[\n]*)\[([^]\[< \t\n]+)([^\n]*);\1<\2>\3\n; +t tagopen; +G; h; s;^[^\n]*\n+;;; x; s;\n.*$;;; + +:attribs +s;class="([^>]+)>[ \t]*\.([^< \t]+);class="\2 \1>;g; t attribs; +s;(<[^/][^>]*)>[ \t]*\.([^< \t]+);\1 class="\2">;g; +s;(<[^/][^>]*)>[ \t]*#([^< \t]+);\1 id="\2">;g; +s;(<[^/][^>]*)>[ \t]*([^ \t=<]+=("[^"]*"|'[^']*'|[^< \t]*));\1 \2>;g; +t attribs; +s;(]+ )?type=(radio|"radio"|'radio')( [^>]+)?)>[ \t]*(checked|selected);\1 checked="checked">;g; +s;(]+ )?type=(checkbox|"checkbox"|'checkbox')( [^>]+)?)>[ \t]*(checked|selected);\1 checked="checked">;g; +s;(]+)?)>[ \t]*(checked|selected);\1 selected="selected">;g; +s;(]+)?)>[ \t]*multiple;\1 multiple="multiple">;g; +t attribs; +s;(<[^/][^>]*>)[ \t]*\.[ \t];\1;g; + +s;(<[^/][^>]*>)[ \t]*;\1;g; +# s;(<[^/][^>]*)>[ \t]*]+>;\1/>;g; +s;(<(br|hr|img|input|link|meta|area|base|col|command|embed|keygen|param|source|track|wbr)[^>]*)>[ \t]*;\1>;g; + +s;; - -
-
- - -
-
-EOF - -list_prescriptions "$client" |grep -q "$edit" || edit_prescription "$edit" - -list_prescriptions "$client" \ -|while read pre; do - [ "$pre" = "$edit" ] \ - && edit_prescription "$pre" \ - || view_prescription "$pre" -done - -# - -# vi:set filetype=html: +locktimeout=900 +. "$_EXEC"/session_lock.sh + +uid="$(timeid)$(randomid)" # 32 Octets UID, starting with timestamp +course="${uid}.ics" + +tzid="$(cat /etc/timezone)" +tstamp="$(TZ="$tzid" date +%Y%m%dT%H%M%S)" + +coursefile="$_DATA/ical/$course" + +if tempfile="$(SLOCK "$coursefile")"; then + cat >"$tempfile" <<-EOF + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:Berlin RAW Confetti + BEGIN:VEVENT + UID:$uid + DTSTAMP:TZID=${tzid}:${tstamp} + DTSTART:TZID=${tzid}:${tstamp} + DURATION: + RRULE: + SUMMARY: + COMMENT: + END:VEVENT + END:VCARD + EOF + REDIRECT "/courses/?e=${course}" +else + SET_COOKIE session message="EDITLOCK" + REDIRECT "/courses/" +fi diff --git a/courses/update_course.sh b/courses/update_course.sh new file mode 100755 index 0000000..4abdbe7 --- /dev/null +++ b/courses/update_course.sh @@ -0,0 +1,153 @@ +#!/bin/sh + +# Copyright 2014, 2015, 2020, 2021 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +. "$_EXEC/pdiread.sh" +. "$_EXEC/session_lock.sh" +. "$_EXEC/cgilite/storage.sh" + +unset coursefile attfile tempfile + +course="$(POST course |PATH)"; course="${course##*/}" +coursefile="$_DATA/ical/$course" +attfile="$_DATA/mappings/attendance" + +if ! tempfile="$(CHECK_SLOCK "$coursefile")"; then + SET_COOKIE 0 message="NO VALID FILE LOCK" + REDIRECT "/courses/?e=${course}" + exit 0 +elif [ "$(POST tid)" != "$(transid "$tempfile")" ]; then + SET_COOKIE 0 message="INVALID TRANSACTION ID" + REDIRECT "/courses/?e=${course}" + exit 0 +fi + +vcf_escape(){ + for each in "$@"; do + printf %s\\n "$each" \ + | sed -E ':X;$!{N;bX}; s;\r\n;\n;g; s;([;,\\]);\\\1;g; s;\n;\\n;g;' + done \ + | sed -E ':X;$!{N;bX}; s;\n;\;;g' +} + +ics="$(pdi_load "$tempfile")" + +tzid=$(cat /etc/timezone) + +ics="$(pdi_update_attrib "$ics" DTSTAMP 1 "TZID=${tzid}")" +ics="$(pdi_update_value "$ics" DTSTAMP 1 "$(TZ="$tzid" date +%Y%m%dT%H%M%S)")" + +dts_year="$( POST DTS_YEAR |grep -m1 -xE '[0-9]{4}' || date +%Y)" +dts_month="$( POST DTS_MONTH |grep -m1 -xE '0[1-9]|1[012]' || date +%m)" +dts_day="$( POST DTS_DAY |grep -m1 -xE '0[1-9]|[12][0-9]|3[01]' || date +%d)" +dts_hour="$( POST DTS_HOUR |grep -m1 -xE '[0-9]|1[0-9]|2[0-3]' || date +%H)" +dts_minute="$(POST DTS_MINUTE |grep -m1 -xE '[0-9]|[1-5][0-9]' || date +%M)" +[ ${#dts_hour} -eq 1 ] && dts_minute="0$dts_hour" +[ ${#dts_minute} -eq 1 ] && dts_minute="0$dts_minute" +DTSTART="${dts_year}${dts_month}${dts_day}T${dts_hour}${dts_minute}00" + +ics="$(pdi_update_attrib "$ics" DTSTART 1 "TZID=${tzid}")" +ics="$(pdi_update_value "$ics" DTSTART 1 "$DTSTART")" + +rr_int=$( POST RRULE_INTERVAL |grep -m1 -xE '[0-9]+' || printf 1) +rr_count=$(POST RRULE_COUNT |grep -m1 -xE '[0-9]+' || printf 1) +rr_freq=$( POST RRULE_FREQ |grep -m1 -xE 'DAILY|WEEKLY|MONTHLY|YEARLY' || printf MONTHLY) +rr_uy=$( POST RRULE_UYEAR |grep -m1 -xE '[0-9]{4}' || date +%Y) +rr_um=$( POST RRULE_UMONTH |grep -m1 -xE '[1-9]|1[012]' || date +%m) +rr_ud=$( POST RRULE_UDAY |grep -m1 -xE '[1-9]|[12][0-9]|3[01]' || date +%d) +[ ${#rr_um} -eq 1 ] && rr_um="0$rr_um" +[ ${#rr_ud} -eq 1 ] && rr_ud="0$rr_ud" + +case $(POST RRULE_LIMIT) in + COUNT) RRULE="FREQ=$rr_freq;INTERVAL=$rr_int;COUNT=$rr_count";; + UNTIL) RRULE="FREQ=$rr_freq;INTERVAL=$rr_int;UNTIL=${rr_uy}${rr_um}${rr_ud}T000000Z";; + ETERN|*) RRULE="FREQ=$rr_freq;INTERVAL=$rr_int";; +esac + +ics="$(pdi_update_value "$ics" RRULE 1 "$RRULE")" + +for field in $(POST_KEYS |grep -xE '[A-Z][A-Z0-9-]*'); do + for cnt in $(seq 1 $(POST_COUNT "$field")); do + case "$field" in + *) + ics="$(pdi_update_value "$ics" "$field" "$cnt" "$(vcf_escape "$(POST "$field" "$cnt")")")" + ;; + esac +done; done + +# delete fields, first mark for deletion using delete_key +# this way the field enumeration is preserved during the process +# finally filter marked lines +delete_key="$(randomid)" +for delete in $(POST_KEYS |grep -xE '[A-Z][A-Z0-9-]*_delete_[0-9]+'); do + f="${delete%%_*}"; c="${delete##*_}"; + [ "$(POST "$delete")" = "true" ] && ics="$(pdi_update_value "$ics" "$f" "$c" "delete=${delete_key}")" +done +ics="$(printf '%s\n' "$ics" |sed -E "/^[^:]+:delete=${delete_key}\$/d")" + +case "$(POST action)" in + addfield) + newfield="$(POST newfield |grep -m 1 -xE '[A-Z][A-Z0-9-]*')" + ics="$(pdi_update_value "$ics" "$newfield" $(( $(pdi_count "$ics" "$newfield") + 1 )) '')" + printf '%s' "$ics" |grep -vx '' >"$tempfile" + REDIRECT "/courses/?e=${course}" + ;; + addfield\ [A-Z]*) + newfield="$(POST action |sed -nE '1s;^addfield ([A-Z][A-Z0-9-]*)$;\1;p')" + ics="$(pdi_update_value "$ics" "$newfield" $(( $(pdi_count "$ics" "$newfield") + 1 )) '')" + printf '%s' "$ics" |grep -vx '' >"$tempfile" + REDIRECT "/courses/?e=${course}" + ;; + update) + if LOCK "$attfile"; then + grep -F "${course} " "$attfile" |while read junk card; do + touch "$_DATA/vcard/${card}" + done + sed -E -i "/^${course} .+\$/d" "$attfile" + seq 1 $(POST_COUNT attendance) |while read n; do + printf '%s %s\n' "$course" "$(POST attendance $n)" + done >>"$attfile" + grep -F "${course} " "$attfile" |while read junk card; do + touch "$_DATA/vcard/${card}" + done + RELEASE "$attfile" + else + SET_COOKIE 0 message="COULD NOT UPDATE COURSE MAPPINGS" + fi + + printf '%s' "$ics" |grep -vx '' >"${tempfile}.cp" + mv "${tempfile}.cp" "$coursefile" + RELEASE_SLOCK "$coursefile" + REDIRECT "/courses/#${course}" + ;; + cancel) + RELEASE_SLOCK "$coursefile" + [ -f "$coursefile" ] \ + && REDIRECT "/courses/#${course}" \ + || REDIRECT "/courses/" + ;; + delete) + rm "$coursefile" + RELEASE_SLOCK "$coursefile" + REDIRECT "/courses/" + ;; + *) + printf '%s' "$ics" |grep -vx '' >"$tempfile" + REDIRECT "/courses/?e=${course}" + ;; +esac diff --git a/courses/widgets.sh b/courses/widgets.sh new file mode 100755 index 0000000..5b5288c --- /dev/null +++ b/courses/widgets.sh @@ -0,0 +1,237 @@ +# Copyright 2014, 2019, 2020 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +check(){ + [ "$1" = "$2" ] && printf 'checked="checked"' +} + +edit="$(GET e)" +order="$(GET o |grep -m1 -xE 'DOW|TOD')" + +w_sort_courses(){ + cat <<-EOF + [form .sort .search action="?" method="GET" + [fieldset .order [legend $(l10n sort_order):] + [radio "order" "DOW" $(check $order DOW) $(l10n order_DOW)] + [radio "order" "TOD" $(check $order TOD) $(l10n order_TOD)] + ] + [submit "" "" $(l10n order_apply)] + ] + EOF +} + +cal_date(){ + { [ $# -eq 0 ] && cat || printf %s "$*"; } |sed -nE ' + 2q + s/^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$/\1-\2-\3 \4:\5:\6 UTC/p;t + s/^TZID=(.+)\:([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$/TZ="\1" \2-\3-\4 \5:\6:\7/p;t + s/^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$/\1-\2-\3 \4:\5:\6/p;t + ' +} + +cal_item(){ + local course="$1" + local item cnt c + shift 1 + + for item in $@; do + cnt="$(pdi_count "$course" "$item")" + + case $item in + SUMMARY) + printf '[h2 ­%s]' "$(pdi_value "$course" SUMMARY)" + ;; + DTSTART) + printf '[span .text .DTSTART %s %s ]' \ + "$(LANG=de_DE.UTF-8 date -d "$(pdi_value "$course" DTSTART |cal_date)" '+%A, %d. %B %Y - %H:%M')" \ + "$(l10n t_oclock)" + ;; + RRULE) + dts_date="$(pdi_value "$course" DTSTART |cal_date)" + rrule=" $(pdi_value "$course" RRULE)" + rr_int="${rrule##*INTERVAL=}"; rr_int="${rr_int%%;*}" + rr_count="${rrule##*COUNT=}"; rr_count="${rr_count%%;*}" + rr_freq="${rrule##*FREQ=}"; rr_freq="${rr_freq%%;*}" + rr_until="${rrule##*UNTIL=}"; rr_until="${rr_until%%;*}" + rr_until="$(cal_date "${rr_until}")" + + [ "$rr_int" -eq 1 ] \ + && printf '[span .text .RRULE %s]' "$(l10n "s$rr_freq")" \ + || printf '[span .text .RRULE %s %s %s]' "$(l10n t_every)" "${rr_int}" "$(l10n $rr_freq)" + case "$rrule $rr_freq" in + *COUNT*DAILY*) + printf '[span .text %s %s]' "$(l10n t_until)" "$(date -d "$dts_date + $((rr_int * rr_count)) day" "+%A %B %d, %Y - %H:%M")" + ;; + *COUNT*WEEKLY*) + printf '[span .text %s %s]' "$(l10n t_until)" "$(date -d "$dts_date + $((rr_int * rr_count)) week" "+%A %B %d, %Y - %H:%M")" + ;; + *COUNT*MONTHLY*) + printf '[span .text %s %s]' "$(l10n t_until)" "$(date -d "$dts_date + $((rr_int * rr_count)) month" "+%A %B %d, %Y - %H:%M")" + ;; + *COUNT*YEARLY*) + printf '[span .text %s %s]' "$(l10n t_until)" "$(date -d "$dts_date + $((rr_int * rr_count)) year" "+%A %B %d, %Y - %H:%M")" + ;; + *UNTIL*) + printf '[span .text %s %s]' "$(l10n t_until)" "$(date -d "$rr_until" "+%A %B %d, %Y - %H:%M")" + ;; + esac + ;; + attendance);; + COMMENT)[ $cnt -gt 0 ] && printf '[h3 %s]' "$(l10n "$item")" + seq 1 $cnt |while read c; do + printf '[p .item .%s . %s]' "$item" \ + "$(pdi_value "$course" "$item" $c |unescape |HTML)" + done + ;; + *)[ $cnt -gt 0 ] && printf '[h3 %s]' "$(l10n "$item")" + seq 1 $cnt |while read c; do + printf '[span .item .%s . %s]' "$item" \ + "$(pdi_value "$course" "$item" $c |unescape |HTML)" + done + ;; + esac + done +} + +edit_item(){ + local course="$1" + local item cnt c + shift 1 + + for item in $@; do + cnt="$(pdi_count "$course" "$item")" + [ "$cnt" -lt 1 ] && cnt=1 + + case $item in + DTSTART) + local dtstart="$(pdi_value "$course" DTSTART |cal_date)" + local ystart="${dtstart%%-*}"; ystart="${ystart##* }" + local mstart="${dtstart#*-}"; mstart="${mstart%%-*}" + local dstart="${dtstart##*-}"; dstart="${dstart%% *}" + local hhstart="${dtstart##* }"; hhstart="${hhstart%%:*}" + local mmstart="${dtstart##* }"; mmstart="${mmstart#*:}"; mmstart="${mmstart%:*}" + local m mn cdow d + + cat <<-EOF + [h3 . $(l10n DTSTART)] + [input type="number" name="DTS_YEAR" value="${ystart}" placeholder="$(l10n YYYY)"] + [select name="DTS_MONTH" onchange="this.form.submit();" + $(m=1; for mn in $(l10n January February March April May June July August September October November December); do + printf ' [option value="%02i" %s . %s]\n' $m "$(selected $m $mstart)" "$mn" + m=$((m+1)) + done) + ][submit "DTS" "update" . $(l10n edit_dtscal)] + [table .dtscalt + [tr $(printf '[th . %s]' $(l10n Mon Tue Wed Thu Fri Sat Sun))] + [tr $( + local cdow d + cdow="$(date -d ${ystart}-${mstart}-1 +%u)" + seq 2 $cdow |xargs -n1 printf '[td .padding .%s]' + d=1; while [ "$d" -lt 29 ] || [ "$(date -d ${ystart}-${mstart}-${d} +%m)" -eq "$mstart" ]; do + [ $cdow -eq 1 -a $d -ne 1 ] && printf ']\n [tr ' + printf '[td [input type="radio" name="DTS_DAY" #DTSCAL_%i value="%02i" %s][label for="DTSCAL_%i" %i]]' \ + $d $d "$(checked $d $dstart)" $d $d + d=$((d + 1)); cdow=$(((cdow + 1) % 7)) + done 2>/dev/null + )] + ] + [label .DTSTIME $(l10n time):] + [input type="number" name="DTS_HOUR" value="$hhstart" min="0" max="23"]:[input type="number" name="DTS_MINUTE" value="$mmstart" min="0" max="59"] + EOF + ;; + RRULE) + local dtstart="$(pdi_value "$course" DTSTART |cal_date)" + local ystart="${dtstart%%-*}"; ystart="${ystart##* }" + local mstart="${dtstart#*-}"; mstart="${mstart%%-*}" + local dstart="${dtstart##*-}"; dstart="${dstart%% *}" + + local rrule="$(pdi_value "$course" RRULE)" + local rr_int="$(printf %s "$rrule" |sed -nE 's;^(.*\;[ ]*)?INTERVAL=([0-9]+)(\;.*)?$;\2;p')" + local rr_count="$(printf %s "$rrule" |sed -nE 's;^(.*\;[ ]*)?COUNT=([0-9]+)(\;.*)?$;\2;p')" + local rr_freq="$(printf %s "$rrule" |sed -nE 's;^(.*\;[ ]*)?FREQ=(DAILY|WEEKLY|MONTHLY|YEARLY)(\;.*)?$;\2;p')" + local rr_until="$(printf %s "$rrule" |sed -nE 's;^(.*\;[ ]*)?UNTIL=([0-9]{8}T[0-9]{6}Z)(\;.*)?$;\2;p')" + local rr_uyear="${rr_until%????T??????Z}" + local rr_umonth=${rr_until#????}; rr_umonth="${rr_umonth%??T??????Z}" + local rr_uday=${rr_until#??????}; rr_uday="${rr_uday%T??????Z}" + local rr_limit="ETERN" + [ "$rr_count" ] && [ "$rr_count" -ge 0 ] && rr_limit="COUNT" + [ "$rr_uyear" ] && [ "$rr_uyear" -ge 0 ] && rr_limit="UNTIL" + + cat <<-EOF + [h3 . $(l10n "$item")] + [span .item . $(l10n t_every) + [input type="number" .RRULE .INTERVAL name="RRULE_INTERVAL" placeholder="#N" value="${rr_int:-1}" min="1"] + [select .RRULE .FREQ name="RRULE_FREQ" + $(for f in DAILY WEEKLY MONTHLY YEARLY; do + printf ' [option value="%s" %s . %s]\n' "$f" "$(selected $f "$rr_freq")" "$(l10n $f)" + done) + ]] + [label .item [input type="radio" name="RRULE_LIMIT" value="ETERN" $(checked "$rr_limit" ETERN)] $(l10n t_eternal)] + [label .item + [input type="radio" name="RRULE_LIMIT" value="COUNT" $(checked "$rr_limit" COUNT)] + [input type="number" .RRULE .COUNT name="RRULE_COUNT" placeholder="#N" value="${rr_count:-1}" min="1"] $(l10n t_times) + ] + [label .item + [input type="radio" name="RRULE_LIMIT" value="UNTIL" $(checked "$rr_limit" UNTIL)] $(l10n t_until) + [input type="number" .RRULE .UYEAR name="RRULE_UYEAR" placeholder="$(l10n YYYY)" value="${rr_uyear:-$ystart}" min="$ystart"] + [input type="number" .RRULE .UMONTH name="RRULE_UMONTH" placeholder="$(l10n MM)" value="${rr_umonth:-$mstart}" min="1" max="12"] + [input type="number" .RRULE .UDAY name="RRULE_UDAY" placeholder="$(l10n DD)" value="${rr_uday:-$dstart}" min="1" max="31"] + ] + EOF + ;; + COMMENT) + printf '[h3 %s]' "$(l10n "$item")" + seq 1 $cnt |while read c; do + printf '[checkbox "%s_delete_%i" "true" .delete #%s_delete_%i][label for="%s_delete_%i" %s]' \ + "$item" $c "$item" $c "$item" $c "$(l10n delete)" + printf '' \ + "$item" "$item" "$(pdi_value "$course" "$item" $c |unescape |HTML)" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + attendance) + printf '[h3 %s]' "$(l10n course_attendance)" + printf '[div .attendance\n' + for vcf in "$_DATA"/vcard/*.vcf; do + fn="$(pdi_value "$(pdi_load "$vcf")" FN)" + printf '%s/%s\n' "${vcf##*/}" "$fn" + done \ + | sort -t/ -k2 \ + | while IFS=/ read -r vcf fn; do + printf '[span .item [input type="checkbox" id="att%s" name="attendance" value="%s" %s][label for="att%s" . %s]]' \ + "$vcf" "$vcf" "$(grep -qxF "${coursefile##*/} $vcf" "$_DATA/mappings/attendance" && printf 'checked="checked"')" "$vcf" "$fn" + done + printf ']' + ;; + SUMMARY) + printf '[h3 %s]' "$(l10n "$item")" + printf '[input .item .%s name="%s" value="%s" placeholder="%s"]' \ + "$item" "$item" "$(pdi_value "$course" "$item" |unescape |HTML)" "$(l10n "$item")" + ;; + *) + printf '[h3 %s]' "$(l10n "$item")" + seq 1 $cnt |while read c; do + printf '[checkbox "%s_delete_%i" "true" .delete #%s_delete_%i][label for="%s_delete_%i" %s]' \ + "$item" $c "$item" $c "$item" $c "$(l10n delete)" + printf '[input .item .%s name="%s" value="%s" placeholder="%s"]' \ + "$item" "$item" "$(pdi_value "$course" "$item" $c |unescape |HTML)" "$(l10n "$item")" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + esac + done +} diff --git a/globals.sh b/globals.sh deleted file mode 100755 index 36498e1..0000000 --- a/globals.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/zsh - -# Copyright 2014 - 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -date() { - LANG=de_DE.UTF-8 LC_ALL=de_DE.UTF-8 /bin/date $@ -} - -PROFILE=$(validate "$PROFILE" 'medical|circus' circus ) - -PAGE="${_GET[p]:-${PAGE:-cards}}" - -uuidgenerator(){ - head -c16 /dev/urandom |sha1sum - |cut -c1-32 -} - -_checked='' -checked(){ - if [ "$_checked" = "$1" ]; then - printf 'checked="checked"' - fi -} - - -VCF_FIELDS=(PHOTO LOGO FN NICKNAME SOUND GENDER KIND TITLE ROLE ORG MEMBER CATEGORIES ANNIVERSARY BDAY EMAIL TEL IMPP ADR URL LANG NOTE RELATED X-ZACK-JOINDATE X-ZACK-LEAVEDATE X-HEALTH-INSURANCE X-HEALTH-INSURANCE-NOCONTRIB X-CLIENT-REFERRAL) - -case "${PROFILE}" in -medical) - data_dirs vcard cache temp mappings prescriptions therapies - [ -z "$NAVIGATION" ] && NAVIGATION=(cards) -;; -circus) - data_dirs vcard ical cache temp mappings - [ -z "$NAVIGATION" ] && NAVIGATION=(cards courses) -;; -esac diff --git a/index.cgi b/index.cgi new file mode 100755 index 0000000..ee0d6f7 --- /dev/null +++ b/index.cgi @@ -0,0 +1,97 @@ +#!/bin/sh + +for n in "$@"; do case ${n%%=*} in + data) _DATA="${n#data=}";; + exec) _EXEC="${n#exec=}";; + debug) DEBUG="${n#debug=}";; +esac; done + +[ ! "${_EXEC%/}" ] && _EXEC="$(realpath "${0%/*}")" || _EXEC="${_EXEC%/}" +[ ! "${_DATA%/}" ] && _DATA=. || _DATA="${_DATA%/}" +[ "$DEBUG" ] && exec 2>>"$DEBUG" + +mkdir -p "${_DATA}/cache" "${_DATA}/mappings" "${_DATA}/export" "${_DATA}/lock" "${_DATA}/ical" "${_DATA}/vcard" + +debug() { + local dbg=/dev/stderr + if [ ! "$DEBUG" ]; then + [ "$#" -gt 0 ] && : || cat; + elif [ "$#" -gt 0 ]; then + printf '%s\n' "$@" >>"$dbg" + else + tee -a "$dbg" + fi +} + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/cgilite/session.sh" + +. "$_EXEC/l10n.sh" + +_PATH="$(PATH "/${PATH_INFO}")" +ACTION="$(GET a)" + +message="$(COOKIE message)" +[ "$message" ] && SET_COOKIE 0 message='' + +checked(){ + if [ "$1" = "$2" ] || [ "$1" -eq "$2" ]; then + printf 'checked="checked"' + fi 2>/dev/null +} +selected(){ + if [ "$1" = "$2" ] || [ "$1" -eq "$2" ]; then + printf 'selected="selected"' + fi 2>/dev/null +} + +yield_page() { + local class="$1" style="$2" + printf 'Content-Type: text/html; charset=utf-8\r\n\r\n' + { printf ' + [html [head + [title Confetti] + [meta name="viewport" content="width=device-width"] + [link rel="stylesheet" type="text/css" href="/style.css"] + ' + [ -n "$style" ] && printf ' + [link rel="stylesheet" type="text/css" href="%s"] + ' "$style" + printf ' + ] [body #top class="%s" + ' "$class" + printf '[ul .menu [li [a "/cards/" . %s]][li [a "/courses/" . %s]]]' "$(l10n cards)" "$(l10n courses)" + [ "$message" ] && printf '[p #message\n%s\n]' "$(l10n "$message")" + cat + printf '] ]' + } \ + | "${_EXEC}/cgilite/html-sh.sed" +} + +topdir="${_PATH#/}" +topdir="/${topdir%%/*}" + +case ${_PATH} in + /) REDIRECT /cards/ + ;; + /export/*.pdf) . "$_EXEC/cgilite/file.sh" + FILE "${_DATA}/${_PATH}" "application/pdf" + ;; + /export/*) . "$_EXEC/cgilite/file.sh" + FILE "${_DATA}/${_PATH}" + ;; + *) + if [ -d "${_EXEC}/${_PATH}" -a -x "${_EXEC}/${_PATH}/index.cgi" ]; then + . "${_EXEC}/${_PATH}/index.cgi" + elif [ -f "${_EXEC}/${_PATH}" -a -x "${_EXEC}/${_PATH}" ]; then + . "${_EXEC}/${_PATH}" + elif [ -f "${_EXEC}/${_PATH}" -a -r "${_EXEC}/${_PATH}" ]; then + . "$_EXEC/cgilite/file.sh" + FILE "${_EXEC}/${_PATH}" + elif [ -d "${_EXEC}/${topdir}" -a -x "${_EXEC}/${topdir}/index.cgi" ]; then + . "${_EXEC}/${topdir}/index.cgi" + else + printf '%s\r\n' 'Status: 404 Not Found' 'Content-Length: 0' '' + fi + ;; +esac diff --git a/l10n.sh b/l10n.sh new file mode 100755 index 0000000..cab5bde --- /dev/null +++ b/l10n.sh @@ -0,0 +1,177 @@ +# Copyright 2014, 2016, 2019, 2021 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +l10n(){ + local word + [ $# -eq 0 ] && read -r word || word="$*" + l10n_global "$word" +} + +l10n_global() { + case $1 in + # Nav Menu + cards) printf %s "Teil­neh­mende";; + courses) printf %s "Kurse";; + + # VCF Default + PHOTO) printf %s "Foto";; + LOGO) printf %s "Logo";; + FN) printf %s "Voller Name";; + N) printf %s "Name";; + n_pre) printf %s "Titel";; + n_first) printf %s "Vorname";; + n_middle) printf %s "Mittel­namen";; + n_last) printf %s "Nachname";; + n_post) printf %s "Zusätze";; + NICKNAME) printf %s "Spitz­name";; + SOUND) printf %s "Aus­sprache";; + GENDER) printf %s "Ge­schlecht";; + KIND) printf %s "Typ";; + TITLE) printf %s "Beruf";; + ROLE) printf %s "Position";; + ORG) printf %s "Orga­ni­sation";; + MEMBER) printf %s "Mitglied";; + CATEGORIES) printf %s "Kategorien";; + ANNIVERSARY) printf %s "Jubiläum";; + BDAY) printf %s "Geburtstag";; + EMAIL) printf %s "E-Mail";; + TEL) printf %s "Telefon";; + teltype) printf %s "Anschluss­typ:";; + TYPE=HOME) printf %s "Privat";; + TYPE=WORK) printf %s "Geschäft­lich";; + TYPE=CELL) printf %s "Mobil";; + TYPE=FAX) printf %s "Fax";; + IMPP) printf %s "Chat";; + ADR) printf %s "Anschrift";; + URL) printf %s "Webseite";; + LANG) printf %s "Sprache";; + NOTE) printf %s "Notiz";; + RELATED) printf %s "Kontakte";; + + # ICS Default + SUMMARY) printf "Bezeichnung";; + COMMENT) printf "Kommentar";; + DTSTART) printf "Beginn";; + DURATION) printf "Dauer";; + RRULE) printf "Regelmäßigkeit";; + DAILY) printf "Tage";; + WEEKLY) printf "Wochen";; + MONTHLY) printf "Monate";; + YEARLY) printf "Jahre";; + sDAILY) printf "Täglich";; + sWEEKLY) printf "Wöchentlich";; + sMONTHLY) printf "Monatlich";; + sYEARLY) printf "Jährlich";; + + # UI labels + year) printf %s "Jahr";; + month) printf %s "Monat";; + day) printf %s "Tag";; + edit) printf %s "Bearbeiten";; + edit_categories) printf %s "Kategorien Bearbeiten";; + vcf_export) printf %s "Vcard Exportieren";; + control) printf %s "Aktionen";; + delete) printf %s "entfernen";; + edit_update) printf %s "Daten übernehmen";; + edit_cancel) printf %s "Abbrechen";; + edit_delete) printf %s "Eintrag löschen";; + edit_addfieldtext) printf %s "Neues Feld";; + edit_addfield) printf %s "+";; + edit_deletefield) printf %s "X";; + + filter_label) printf %s "Filter";; + filter_item) printf %s "Eingrenzung nach";; + filter_placeholder) printf %s "Begriffe zur Eingrenzung eingeben";; + filter_type) printf %s "Filter­typ";; + filter_order) printf %s "Sortie­rung";; + filter_any) printf %s "Alles";; + filter_name) printf %s "Name";; + filter_firstname) printf %s "Vor­name";; + filter_lastname) printf %s "Nach­name";; + filter_street) printf %s "Straße";; + filter_zip) printf %s "PLZ.";; + filter_TEL) printf %s "Tele­fon";; + filter_BDAY) printf %s "Geburts­jahr";; + filter_bdate) printf %s "Geburts­datum";; + filter_course) printf %s "Kurs";; + filter_CATEGORIES) printf %s "Kate­go­rien";; + filter_more) printf %s "+ mehr Filter";; + filter_apply) printf %s "Filtern";; + filter_cancel) printf %s "Filter löschen";; + export_csv) printf %s "Liste als CSV-Datei";; + + # UI Labels Special + course_attendance) printf %s "Kurs­teil­nahme";; + vcf_seed_label) printf "Anmeld. Vorn. Nachn. Geb. Monat Geb. Jahr Tel. Mobil () EMail () Notiz";; + + gender_none) printf %s "keine Angabe";; + gender_female) printf %s "Weiblich";; + gender_male) printf %s "Männlich";; + gender_other) printf %s "Sonstiges";; + + female) printf %s "♀";; + male) printf %s "♂";; + other) printf %s "⚥";; + none) printf %s "⚪";; + + # Fallback + *) printf %s "$word";; + esac +} + +l10n_time() { + [ $# -eq 0 ] && read -r time || time="$*" + printf '%s\n' "$time" |sed -E ' + s;Monday;Mon\­\;tag;g; s;Mon\.;Mo.;g; + s;Tuesday;Diens\­\;tag;g; s;Tue\.;Di.;g; + s;Wednesday;Mitt\­\;woch;g; s;Wed\.;Mi.;g; + s;Thursday;Don\­\;ners\­\;tag;g; s;Thu\.;Do.;g; + s;Friday;Frei\­\;tag;g; s;Fri\.;Fr.;g; + s;Saturday;Sams\­\;tag;g; s;Sat\.;Sa.;g; + s;Sunday;Sonn\­\;tag;g; s;Sun\.;So.;g; + + s;January;Ja\­\;nu\­\;ar;g; s;Jan\.;Jan.;g; + s;February;Fe\­\;bru\­\;ar;g; s;Feb\.;Feb.;g; + s;March;März;g; s;Mar\.;Mär.;g; + s;April;April;g; s;Apr\.;Apr.;g; + s;May;Mai;g; s;May\.;Mai.;g; + s;June;Juni;g; s;Jun\.;Jun.;g; + s;July;Juli;g; s;Jul\.;Jul.;g; + s;August;Au\­\;gust;g; s;Aug\.;Aug.;g; + s;September;Sep\­\;tem\­\;ber;g; s;Sep\.;Sep.;g; + s;October;Ok\­\;to\­\;ber;g; s;Oct\.;Okt.;g; + s;November;No\­\;vem\­\;ber;g; s;Nov\.;Nov.;g; + s;December;De\­\;zem\­\;ber;g; s;Dec\.;Dez.;g; + ' +} + +parse_date() { + [ $# -eq 0 ] && read -r date || date="$*" + + case $date in + *[0-9].*[0-9].*[0-9]) + d="${date%%.*}" + y="${date##*.}" + m="${date%.*}" + m="${m#*.}" + [ $y -lt 100 ] && y="$((y + 2000))" + date -d "$(printf '%04i-%02i-%02i' "$y" "$m" "$d")" +%F + ;; + *) date -d "$date" +%F + ;; + esac +} diff --git a/pages/cards.sh b/pages/cards.sh deleted file mode 100755 index 538146d..0000000 --- a/pages/cards.sh +++ /dev/null @@ -1,281 +0,0 @@ -#!/bin/zsh - -# Copyright 2014 - 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -BR=' -' - -force_items(){ - for each in "$@"; do - [ -z "${values[$each]+x}" ] && values[${each}]='' - done -} - -case $PROFILE in -medical) - SUP_FIELDS=(N NICKNAME GENDER BDAY ADR TEL EMAIL X-HEALTH-INSURANCE X-HEALTH-INSURANCE-NOCONTRIB IMPP URL NOTE X-CLIENT-REFERRAL) - FORCE_ITEMS=(ADR TEL EMAIL NOTE X-CLIENT-REFERRAL) - _GET[order]="${_GET[order]:-lastname}" - _GET[filtertype]="${_GET[filtertype]:-name}" - profile_medical=x -;; -circus) - SUP_FIELDS=(N NICKNAME GENDER BDAY X-ZACK-JOINDATE X-ZACK-LEAVEDATE EMAIL TEL IMPP ADR URL NOTE) - FORCE_ITEMS=(BDAY X-ZACK-JOINDATE TEL EMAIL ADR NOTE) - _GET[order]="${_GET[order]:-firstname}" - _GET[filtertype]="${_GET[filtertype]:-any}" - profile_circus=x -;; -esac - -edit="${_GET[edit]}" -[ \! -f "vcard/$edit" -a \! -f "temp/$edit" ] && edit='' - -listcourses() { - ls -1 ${_DATA}/ical/*ics |while read file; do - icstime="$(sed -rn 's:^DTSTART\:(TZID=.*\:)?([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z?\r$:\2-\3-\4 \5\:\6\:\7:p' "$file")" - echo "$(date -d "$icstime" "+%u %H%M%S")\t$file" - done |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:' -} - -list_hi_companies(){ - sed -rn 's;^X-HEALTH-INSURANCE:([^\;]+)\;.*$;\1;p' ${_DATA}/vcard/*vcf \ - | sort -u -} - -list_categories() { - catfile="${_DATA}/mappings/categories" - sort -u "$catfile" \ - | sed -r '/^[\t ]*$/d' -} - -listcards_order(){ - while read file; do - case "${_GET[order]}" in - firstname) - printf '%s\t%s\n' "$(sed -rn 's:^N(;.+)*\:([^;]*;){1} *([^;]*).*$:\3:p' "$file")" "$file" - ;; - lastname) - printf '%s\t%s\n' "$(sed -rn 's:^N(;.+)*\:([^;]*;){0} *([^;]*).*$:\3:p' "$file")" "$file" - ;; - bdate) - printf '%s\t%s\n' "$(sed -rn 's:^BDAY(;.+)*\:(.*)$:\2:p' "$file")" "$file" - ;; - *) printf 'x\t%s\n' "$file" - ;; - esac - done \ - | sort -u |sed -r 's;^.*\t;;' -} - -listcards_filter(){ - filterex='s;^([^\n]+)\n.*$;\1;p' - printf '%s\n' "${_GET[filter]}" |tr '^' '\n' \ - | sed -r 's;[]\/\(\)\\\^\$\?\.\+\*\;\[\{\}];\\\\&;g' \ - | while read each; do - case $each in - name:*) expr='(FN|NICKNAME|N)(\;[^\n]+)*:[^\n]*'"(${each#*:})";; - street:*) expr='ADR(\;[^\n]+)*:([^\;]*;){2}[^\;\n]*'"(${each#*:})";; - zip:*) expr='ADR(\;[^\n]+)*:([^\;]*;){5}[^\;\n]*'"(${each#*:})";; - any:*|:*) expr="[^\n]*"'(\;[^\n]+)*:[^\n]*'"(${each#*:})";; - *:*) expr="${each%%:*}"'(\;[^\n]+)*:[^\n]*'"(${each#*:})";; - *) expr="(${each})";; - esac - filterex='/(^|\n)'"${expr}"'/I{'"${filterex}"'}' - done - - while read n; do - { printf '%s\n' "$n"; cat "$n"; } \ - | sed -En ':X;N;$!bX; {'"$filterex"'}' - done - -} - -listcards() { - printf %s\\n "${_DATA}/vcard/"*.vcf \ - | listcards_filter \ - | listcards_order \ - | sed -E 's;^(.*/)*;;;' -} - -listcards_mail() { - printf %s\\n "${_DATA}/vcard/"*.vcf \ - | listcards_filter \ - | xargs -r -d\\n sed -En 's:^EMAIL(;.+)*\:(.+)\r$:\2,:p' -} - -vcf_parse() { - unset key - sed -r ':X;N;$!bX; s;\r\n[ \t];;g; s;\r\n;\n;g;' "$1" \ - | sed -rn ' - # === turn property names to upper case, strip group names === - h; s;^([^;:]+);;; - x; s;^([^;:\.]+\.)?([^;:]+).*$;\2;; - y;abcdefghijklmnopqrstuvwxyz;ABCDEFGHIJKLMNOPQRSTUVWXYZ; - G; s;\n;;; - - # === strip trailing CR (but keep CRs in property value) === - s;\r$;;; - - # === Normalise various known vendor properties === - s;^X-MS-CARDPICTURE(\;|:);PHOTO\1;; - s;^X-GENDER(\;|:);GENDER\1;; - s;^X-ANNIVERSARY(\;|:);ANNIVERSARY\1;; - s;^X-EVOLUTION-ANNIVERSARY(\;|:);ANNIVERSARY\1;; - s;^X-KADDRESSBOOK-X-ANNIVERSARY(\;|:);ANNIVERSARY\1;; - s;^X-EVOLUTION-BLOG-URL(\;|:);URL\1;; - s;^AGENT(\;|:);RELATED\;VALUE=text\;TYPE=agent\1;; - s;^X-ASSISTANT(\;|:);RELATED\;VALUE=text\;TYPE=assistant\1;; - s;^X-EVOLUTION-ASSISTANT(\;|:);RELATED\;VALUE=text\;TYPE=assistant\1;; - s;^X-KADDRESSBOOK-X-ASSISTANTSNAME(\;|:);RELATED\;VALUE=text\;TYPE=assistant\1;; - s;^X-MANAGER(\;|:);RELATED\;VALUE=text\;TYPE=manager\1;; - s;^X-EVOLUTION-MANAGER(\;|:);RELATED\;VALUE=text\;TYPE=manager\1;; - s;^X-KADDRESSBOOK-X-MANAGERSNAME(\;|:);RELATED\;VALUE=text\;TYPE=manager\1;; - s;^X-SPOUSE(\;|:);RELATED\;VALUE=text\;TYPE=spouse\1;; - s;^X-EVOLUTION-SPOUSE(\;|:);RELATED\;VALUE=text\;TYPE=spouse\1;; - s;^X-KADDRESSBOOK-X-SPOUSENAME(\;|:);RELATED\;VALUE=text\;TYPE=spouse\1;; - - # === Normalise obsolete vendor IM properties === - s;^X-AIM((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:aim:;; - s;^X-ICQ((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:aim:;; - s;^X-GOOGLE-TALK((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:xmpp:;; - s;^X-JABBER((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:xmpp:;; - s;^X-MSN((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:msn:;; - s;^X-YAHOO((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;IMPP\1:ymsgr:;; - s;^X-SIP((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(sip:)?;IMPP\1:sip:;; - - # === Update obsolete LABEL property === - s;^LABEL((\;[A-z0-9-]+|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$;ADR\1\;LABEL="\5":;; - - /^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$/{ - h; - s;^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$;key='\''\1'\'';; - H; g; - - s;^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):([^\n]*)\n.*$;\2;; - s;'\'';'\'\\\\\'\'';g - s;\;([A-Z0-9-]+)(=|=(([^\;,:"]+|"[^"]+")(,[^\;,:"]+|,"[^"]+")*))?;\ntag[\1]='\''\3'\'';g - s;^\n+;;; s;\n+$;;; - /^.+$/H; g; - - s;^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):([^\n]*)\n.*$;\6;; - # :X s;((^|[^\\])(\\\\)*)[;,];\1\n;g; s;((^|[^\\])(\\\\)*)\\([;,]);\4;g; tX; - s;'\'';'\'\\\\\'\'';g - s;^.*$;value='\''&'\''; - H; g; - - s;^[^\n]*[\n ]+;; - s;\n+$;;; - - p; - } - ' \ - | while read -r line; do - declare -A tag - case "$line" in - value*) eval "$line";; - tag*) eval "$line";; - key*) - if [ -z "$key" ]; then - eval "$line" - else - printf '%s\n' "$value" |sed -rn ' - :X - s;((^|[^\\])(\\\\)*),;\1\n;g; - tX; - s;\\,;,;g; - p - ' \ - | while read -r val; do - while [ -n "${values[$key$n]+x}" ]; do n=$((${n=-1} + 1)); done - if printf '%s\n' "$val" |grep -qE '((^|[^\\])(\\\\)*)\;'; then - m=0 - values[${key}${n}]="${val}" - printf '%s\n' "$val" |sed -rn ' - :X - s;((^|[^\\])(\\\\)*)\;;\1\n;g; - tX; - s;\\\;;\;;g; - p - ' \ - | while read -r v; do - values[${key}${n}+${m}]="$( - printf %s\\n "${v}" \ - | sed -rn ' - :X - s;((^|[^\\])(\\\\)*)\\n;\1\n;g; - tX; - s;\\\\;\\;g; - p - ' - )" - m=$(($m + 1)) - done - else - values[${key}${n}]="$( - printf %s\\n "${val}" \ - | sed -rn ' - :X - s;((^|[^\\])(\\\\)*)\\n;\1\n;g; - tX; - s;\\\;;\;;g; - s;\\\\;\\;g; - p - ' - )" - fi - for t in ${(k)tag}; do - values[${key}${n}_${t}]="${tag[$t]}" - done - done - - eval "$line" - unset n - while [ -n "${values[$key$n]+x}" ]; do n=$((${n=-1} + 1)); done - unset value - unset tag - fi - ;; - esac - done -} - -view_card() { #Parameter: Cardfile - id="$1" - cardfile="$_DATA/vcard/${id}" - cachefile="$_DATA/cache/${id}.cache" - if [ "$cachefile" -nt "$cardfile" ]; then - cat "$cachefile" - else - declare -A values - vcf_parse "$cardfile" - . "$_EXEC/templates/view_card.sh" |tee "$cachefile" - fi -} - -edit_card() { #Parameter: Cardfile - id="$1" - cardfile="$_DATA/vcard/$id" - tempfile="$_DATA/temp/$id" - [ -f "$tempfile" ] && cardfile="$tempfile" - - declare -A values - vcf_parse "$cardfile" - force_items $FORCE_ITEMS - . "$_EXEC/templates/edit_card.sh" -} diff --git a/pages/categories.sh b/pages/categories.sh deleted file mode 100755 index e875eef..0000000 --- a/pages/categories.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/zsh - -# Copyright 2015, 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -catfile="${_DATA}/mappings/categories" - -listcards() { - for file in "${_DATA}/vcard/"*.vcf; do - printf '%s\t%s\n' \ - "$(sed -rn 's:^N(;.+)*\:([^;]*;){1} *([^;]*).*$:\3:p' "$file")" \ - "$file" - done \ - | sort \ - | sed -r 's;^.*\t(.*/)([^/]+)$;\2;' -} - -get_name() { - cfile="${_DATA}/vcard/$1" - sed -rn 's;^N(\;[^":]+|\;"[^"]+")*:([^\;]*)(\;[^\;]*)(\;[^\;]*)?(\;[^\;]*)?(\;[^\;]*)?\r?$;\5 \3 \4 \2 \6;; - tX; b; :X s;[,\; ]+; ;g; p;' "$cfile" -} - -get_categories(){ - cfile="${_DATA}/vcard/$1" - sed -rn 's;^CATEGORIES(\;[^":]+|\;"[^"]+")*:(.+)\r?$;\2;; - tX; b; :X - s;(^|[^\\]+)((\\\\)+),;\1\2\n;g; - s;(^|[^\\]),;\1\n;g; s;(^|[^\\]+)((\\\\)+),;\1\2\n;g; - s;(^|[^\\]),;\1\n;g; s;\\,;,;g; p;' "$cfile" \ - | sort -u -} - -list_categories() { - sort -u "$catfile" \ - | sed -r '/^[\t ]*$/d' -} diff --git a/pages/courses.sh b/pages/courses.sh deleted file mode 100755 index 93e8261..0000000 --- a/pages/courses.sh +++ /dev/null @@ -1,171 +0,0 @@ -#!/bin/zsh - -# Copyright 2014, 2016, 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -[ -z "${_GET[order]}" ] && _GET[order]=DOW - -listcards() { - ls -1 ${_DATA}/vcard/*vcf 2>/dev/null |while read file; do - fn=$(sed -rn 's:^N(;.+)*\:([^;]*;){1} *([^;]*).*$:\3:p' "$file") - echo "$fn\t$file" - done |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:' -} - - -listcourses() { - case "${_GET[filtertype]}" in - *) ls -1 ${_DATA}/ical/*ics 2>/dev/null - ;; - esac |case "${_GET[order]}" in - DOW) - while read file; do - icstime="$(sed -rn 's:^DTSTART\:(TZID=.*\:)?([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z?\r$:\2-\3-\4 \5\:\6\:\7:p' "$file")" - echo "$(date -d "$icstime" "+%u %H%M%S")\t$file" - done - ;; - TOD) - while read file; do - icstime="$(sed -rn 's:^DTSTART\:(TZID=.*\:)?([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z?\r$:\2-\3-\4 \5\:\6\:\7:p' "$file")" - echo "$(date -d "$icstime" +%H%M%S)\t$file" - done - ;; - *) - sed -r 's:^.*$:x\t&:' - ;; - esac |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:' -} - -list_attendance() { - id="$1" - sed -rn 's:'$id'\t(.+)$:\1:p' "$_DATA/mappings/attendance" |while read each; do - card="$_DATA/vcard/$each" - if [ -r "$card" ]; then - aname="$(sed -rn ' - /^N(;.+)*:/{ - h; - s;^N(\;.+)*:([^\;]*\;){3} *([^\;]*).*$;\3;p; g; - s;^N(\;.+)*:([^\;]*\;){1} *([^\;]*).*$;\3;p; g; - s;^N(\;.+)*:([^\;]*\;){2} *([^\;]*).*$;\3;p; g; - s;^N(\;.+)*:([^\;]*\;){0} *([^\;]*).*$;\3;p; g; - s;^N(\;.+)*:([^\;]*\;){4} *([^\;]*).*$;\3;p; g; - }' "$card" \ - | sed -r ':X;N;$!bX; s;([\;\n\r,]| )+; ;g;; s;^ +| +$;;g;' - )" - fname="$(sed -rn 's;^FN(\;.+)*:(.+)\r?$;\2;g; s;([\;\n,]| )+; ;g;; s;^ +| +$;;g;' "$card")" - nname="$(sed -rn 's;^NICKNAME(\;.+)*:(.+)\r?$;\2;g; s;([\;\n,]| )+; ;g;; s;^ +| +$;;g;' "$card")" - byear="$(sed -rn 's:^BDAY(\;.+)*\:([0-9]{4})(-[0-9][0-9]){2}.*$:\2:p' "$_DATA/vcard/$each")" - - printf '%s %s (*%04i)\n' "$each" "${aname:-${fname:-${nname}}}" "$byear" - fi - done -} - -course_mail() { - id="$1" - coursemail="" - sed -rn 's:'$id'\t(.+)$:\1:p' "$_DATA/mappings/attendance" |while read each; do - coursemail="$coursemail$(sed -rn 's:^EMAIL(;.+)*\:(.+)\r$:\2,:p' "$_DATA/vcard/$each")" - done - echo "$coursemail" -} - -ics_parse() { - unset key - sed -r ':X;N;$!bX; s;\r\n[ \t];;g; s;\r\n;\n;g;' "$1" \ - | sed -rn ' - # === turn property names to upper case, strip group names === - h; s;^([^;:]+);;; - x; s;^([^;:\.]+\.)?([^;:]+).*$;\2;; - y;abcdefghijklmnopqrstuvwxyz;ABCDEFGHIJKLMNOPQRSTUVWXYZ; - G; s;\n;;; - - /^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$/{ - h; - s;^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)$;key='\''\1'\'';; - H; g; - - s;^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):([^\n]*)\n.*$;\2;; - s;'\'';'\'\\\\\'\'';g - s;\;([A-Z0-9-]+)(=|=(([^\;,:"]+|"[^"]+")(,[^\;,:"]+|,"[^"]+")*))?;\ntag[\1]='\''\3'\'';g - s;^\n+;;; s;\n+$;;; - /^.+$/H; g; - - s;^([A-Z0-9-]+)((\;[A-z0-9-]+=?|\;[A-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):([^\n]*)\n.*$;\6;; - # :X s;((^|[^\\])(\\\\)*)[;,];\1\n;g; s;((^|[^\\])(\\\\)*)\\([;,]);\4;g; tX; - s;'\'';'\'\\\\\'\'';g - s;^.*$;value='\''&'\''; - H; g; - - s;^[^\n]*[\n ]+;; - s;\n+$;;; - - p; - } - ' \ - | while read -r line; do - declare -A tag - case "$line" in - value*) eval "$line";; - tag*) eval "$line";; - key*) - if [ -z "$key" ]; then - eval "$line" - else - values[$key]="$value" - for t in ${(k)tag}; do - values[${key}_$t]="$tag[$t]" - done - eval "$line" - if [ -n "$values[$key]" ]; then - n=0 - while [ -n "$values[$key$n]" ]; do n=$(($n + 1)); done - key=$key$n - fi - unset value - unset tag - fi - ;; - esac - done -} - -view_course() { #Parameter: Calendarfile - id="$1" - calendarfile="$_DATA/ical/${id}" - cachefile="$_DATA/cache/${id}.cache" - unset key - if [ "$cachefile" -nt "$calendarfile" ]; then - cat "$cachefile" - else - declare -A values - ics_parse "$calendarfile" - . ${_EXEC}/templates/view_course.sh |tee "$cachefile" - fi -} - -edit_course() { #Parameter: Calendarfile - id="$1" - calendarfile="$_DATA/ical/$id" - tempfile="$_DATA/temp/$id" - [ -f "$tempfile" ] && calendarfile="$tempfile" - unset key - - declare -A values - ics_parse "$calendarfile" - . ${_EXEC}/templates/edit_course.sh -} diff --git a/pages/email.sh b/pages/email.sh deleted file mode 100755 index c79876f..0000000 --- a/pages/email.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/zsh - -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -[ -z "${_GET[order]}" ] && _GET[order]=DOW - -listcards() { - ls -1 ${_DATA}/vcard/*vcf 2>/dev/null |while read file; do - fn=$(sed -rn 's:^N(;.+)*\:([^;]*;){1} *([^;]*).*$:\3:p' "$file") - echo "$fn\t$file" - done |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:' -} - -listcourses() { - ls -1 ${_DATA}/ical/*ics |while read file; do - icstime="$(sed -rn 's:^DTSTART\:(TZID=.*\:)?([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z?\r$:\2-\3-\4 \5\:\6\:\7:p' "$file")" - echo "$(date -d "$icstime" "+%u %H%M%S")\t$file" - done |sort |sed -r 's:^.*\t(.*/)([^/]+)$:\2:' -} diff --git a/pages/prescriptions.sh b/pages/prescriptions.sh deleted file mode 100755 index fcef09a..0000000 --- a/pages/prescriptions.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/zsh - -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -source "$_EXEC/pages/cards.sh" -declare -A mpx - -BR=' -' - -view_card="$_EXEC/templates/view_client.sh" - -list_prescriptions(){ - client="$1" - find "$_DATA/prescriptions/" -name "${client%.vcf}.*.mpx" \ - | while read pfile; do - printf '%s\t%s\n' "$(grep '^date' "$pfile")" "${pfile##*/}" - done \ - | sort -r | cut -f2 -} - -list_prescription_issuers(){ - sed -rn 's;^issuer:(.+)$;\1;p' ${_DATA}/prescriptions/*.mpx \ - | sort -u -} - -edit_prescription(){ - id="$1" - prescfile="$_DATA/prescriptions/$id" - tempfile="$_DATA/temp/$id" - [ -f "$tempfile" ] || cp "$prescfile" "$tempfile" - - mpx=() - cat "$tempfile" |while read -r line; do - val="${line#*:}" - mpx[${line%%:*}]="${val//\\n/$BR}" - done - - . "$_EXEC/templates/edit_prescription.sh" -} - -view_prescription(){ - id="$1" - prescfile="$_DATA/prescriptions/$id" - - mpx=() - cat "$prescfile" |while read -r line; do - val="${line#*:}" - mpx[${line%%:*}]="$(htmlsafe "${val//\\n/$BR}")" - done - - . "$_EXEC/templates/view_prescription.sh" -} - -therapy_dates(){ - tpyfile="$_DATA/therapies/${1%.mpx}.tpy" - sed -rn 's;^session[0-9]+_date:(.+)$;\1;p' "$tpyfile" -} diff --git a/pages/therapy.sh b/pages/therapy.sh deleted file mode 100755 index 8885515..0000000 --- a/pages/therapy.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/zsh - -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -declare -A tpy -declare -A mpx - -BR=' -' - -id="$_GET[id]" - -tpyfile="$_DATA/therapies/$id" -mpxfile="$_DATA/prescriptions/${id%.tpy}.mpx" -client="$_DATA/vcard/${id%%.*}.vcf" - -identify "$_EXEC/static/therapy_background.png" \ -| sed -r 's;^.* ([0-9]+x[0-9]+) .*$;\1;' \ -| read bg_dim - -card_N="$(sed -nr 's:^(N)(;[^"\:]+|;"[^"]+")*\:(.*)$:\3:gp' "$client")" -card_FN="$(sed -nr 's:^(FN)(;[^"\:]+|;"[^"]+")*\:(.*)$:\3:gp' "$client")" -card_NICK="$(sed -nr 's:^(NICKNAME)(;[^"\:]+|;"[^"]+")*\:(.*)$:\3:gp' "$client")" - -n=$(printf %s "$card_N" \ - | sed -rn 's:^([^;]*)(\;[^;]*)(\;[^;]*)?(\;[^;]*)?(\;[^;]*)?$:\4 \2 \3 \1 \5:gp' \ - | sed -r 's:,: :;s:\;: :g;s: +: :g;s:^ $::;' - ) -client_name="${n:-${card_FN:-${card_NICK}}}" - -mpx=() -cat "$mpxfile" |while read -r line; do - val="${line#*:}" - mpx[${line%%:*}]="$(htmlsafe "${val//\\n/$BR}")" -done - -tpy=() -cat "$tpyfile" |while read -r line; do - val="${line#*:}" - tpy[${line%%:*}]="$(htmlsafe "${val//\\n/$BR}")" -done diff --git a/pdiread.sh b/pdiread.sh new file mode 100755 index 0000000..08fbaec --- /dev/null +++ b/pdiread.sh @@ -0,0 +1,196 @@ +#!/bin/zsh + +# Copyright 2014 - 2018 Paul Hänsch +# +# This file is part of Confetti. +# +# Confetti is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Confetti is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Confetti. If not, see . + +# This is a parsing library for the Personal Data Interchange format (PDI) +# PDI is the format for encoding VCard (.vcf) and iCalendar (.ics) files + +[ -n "$include_pdi" ] && return 0 +include_pdi="$0" + +BR=' +' + +unescape() { + local unescape='s;(^(\\\\)*|[^\\](\\\\)*)\\n;\1\n;g; s;\\(.);\1;g' + if [ $# -eq 0 ]; then + sed -E "$unescape" + else + printf %s "$*" \ + | sed -E "$unescape" + fi +} + +pdi_load() { + # normalise PDI file for processing with pdi_* functions + # functions in this library can only be applied to normalised data + # Usage example: + # data="$(pdi_load file.vcf)" + + sed -srn ' + # === Read entire file into buffer === + :X $bY; N; bX; :Y s;^.*$;\n&\n;; + + # === Join continuing lines, strip trailing CRs === + s;\r*\n[ \t];;g; + s;\r*\n;\n;g; + + # === turn property names to upper case, strip group names === + s;\n([^;:\.\n]+\.)([^;:\n]+);\n\2;g; + :upcase + s;(\n[^;:]*)a;\1A;g; s;(\n[^;:]*)b;\1B;g; s;(\n[^;:]*)c;\1C;g; s;(\n[^;:]*)d;\1D;g; s;(\n[^;:]*)e;\1E;g; + s;(\n[^;:]*)f;\1F;g; s;(\n[^;:]*)g;\1G;g; s;(\n[^;:]*)h;\1H;g; s;(\n[^;:]*)i;\1I;g; s;(\n[^;:]*)j;\1J;g; + s;(\n[^;:]*)k;\1K;g; s;(\n[^;:]*)l;\1L;g; s;(\n[^;:]*)m;\1M;g; s;(\n[^;:]*)n;\1N;g; s;(\n[^;:]*)o;\1O;g; + s;(\n[^;:]*)p;\1P;g; s;(\n[^;:]*)q;\1Q;g; s;(\n[^;:]*)r;\1R;g; s;(\n[^;:]*)s;\1S;g; s;(\n[^;:]*)t;\1T;g; + s;(\n[^;:]*)u;\1U;g; s;(\n[^;:]*)v;\1V;g; s;(\n[^;:]*)w;\1W;g; s;(\n[^;:]*)x;\1X;g; s;(\n[^;:]*)y;\1Y;g; + s;(\n[^;:]*)z;\1Z;g; + t upcase; + + # === Insert empty attribute fields where no attributes are present === + s;\n([^;:]+):;\n\1\;:;g; + + # === Unscramble aggregated fields === + :disag + s;\n([^:\n]+:)(([^\n]*[^\])?(\\\\)*),;\n\1\2\n\1;; + t disag; + + # === Insert FN when only N is present === + /\nFN[;:]/!{ + s,\nN(;[^:]*)?:([^;\n]*);([^;\n]*);([^;\n]*);([^;\n]*);([^;\n]*);?\n,&FN;:\5 \3 \4 \2 \6\n,; + :despace + s,(\nFN;:[^\n]*) ([^\n]*\n),\1 \2,; + s,(\nFN;:) ([^\n]*\n),\1\2,; + s,(\nFN;:[^\n]*) (\n),\1\2,; + t despace; + } + /\nFN[;:]/!{ s,\n(N[;:][^\n]*)\n,&F\1\n,; } # Fallback + + # === Normalise various known vendor properties === + s;\nX-MS-CARDPICTURE(\;|:);\nPHOTO\1;g; + s;\nX-GENDER(\;|:);\nGENDER\1;g; + s;\nX-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g; + s;\nX-EVOLUTION-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g; + s;\nX-KADDRESSBOOK-X-ANNIVERSARY(\;|:);\nANNIVERSARY\1;g; + s;\nX-EVOLUTION-BLOG-URL(\;|:);\nURL\1;g; + s;\nAGENT(\;|:);\nRELATED\;VALUE=text\;TYPE=agent\1;g; + s;\nX-ASSISTANT(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g; + s;\nX-EVOLUTION-ASSISTANT(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g; + s;\nX-KADDRESSBOOK-X-ASSISTANTSNAME(\;|:);\nRELATED\;VALUE=text\;TYPE=assistant\1;g; + s;\nX-MANAGER(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g; + s;\nX-EVOLUTION-MANAGER(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g; + s;\nX-KADDRESSBOOK-X-MANAGERSNAME(\;|:);\nRELATED\;VALUE=text\;TYPE=manager\1;g; + s;\nX-SPOUSE(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g; + s;\nX-EVOLUTION-SPOUSE(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g; + s;\nX-KADDRESSBOOK-X-SPOUSENAME(\;|:);\nRELATED\;VALUE=text\;TYPE=spouse\1;g; + + # === Normalise obsolete vendor IM properties === + s;\nX-AIM((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:aim:;g; + s;\nX-ICQ((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:aim:;g; + s;\nX-GOOGLE-TALK((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:xmpp:;g; + s;\nX-JABBER((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:xmpp:;g; + s;\nX-MSN((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:msn:;g; + s;\nX-YAHOO((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):;\nIMPP\1:ymsgr:;g; + s;\nX-SIP((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(sip:)?;\nIMPP\1:sip:;g; + + # === Update obsolete LABEL property === + s;\nLABEL((\;[A-Za-z0-9-]+|\;[A-Za-z0-9-]+=([^;,:"]+|"[^"]+")(,[^;,:"]+|,"[^"]+")*)*):(.*)\n;\nADR\1\;LABEL="\5":\n;g; + + p;' "$@" +} + +pdi_count(){ + local card="$1" name="$2" rc='' cnt=0 + while rc="${card#*${BR}${name};}"; do + [ "${rc}" != "${card}" ] || break + card="$rc" + cnt=$(($cnt + 1)) + done + printf %i\\n $cnt +} + +pdi_attrib(){ + local card=":$1" name="$2" cnt="${3:-1}" attr="$4" + while [ $cnt -gt 0 ]; do + [ "${card#*${BR}${name};}" = "$card" ] && return 1 + card="${card#*${BR}${name};}" + cnt=$((cnt - 1)) + done + card="${card%%:*}" + if [ "$attr" ]; then + case $card in + *\;"$attr"=*) card="${card#*;${attr}=}";; + "$attr"=*) card="${card#${attr}=}";; + "$attr"|*\;"$attr"|"$attr"\;*|*\;"$attr"\;*) return 0;; + *) return 1;; + esac + case $card in + \"*\"\;*|\'*\'\;*) + card="${card#[\"\']}"; card="${card%%[\"\'];*}" + ;; + \"*\"|\'*\') + card="${card#[\"\']}"; card="${card%%[\"\']}" + ;; + *\;*) card="${card%%;*}";; + esac + fi + printf %s\\n "${card}" +} + +pdi_value(){ + local card="${BR}$1" name="$2" cnt="${3:-1}" + while [ "$cnt" -gt 0 ]; do + [ "${card#*${BR}${name};*:}" = "$card" ] && return 1 + card="${card#*${BR}${name};*:}" + cnt=$((cnt - 1)) + done + printf %s\\n "${card%%${BR}*}" +} + +pdi_update_value(){ + local card="${BR}$1" name="$2" cnt="$3" val="$4" + while [ "$cnt" -gt 0 ]; do + if [ "${card#*${BR}${name};*:}" = "${card}" ]; then + printf '%s\n%s;:' "${card%${BR}END;:VCARD*}" "${name}" + card="${BR}END;:VCARD" + break; + else + printf '%s\n%s;' "${card%%${BR}${name};*}" "${name}" + card="${card#*${BR}${name};}" + printf '%s:' "${card%%:*}" + card="${card#*:}" + fi + cnt=$((cnt - 1)) + done + printf '%s\n%s\n' "$val" "${card#*${BR}}" +} + +pdi_update_attrib(){ + local card="${BR}$1" name="$2" cnt="$3" val="$4" + while [ "$cnt" -gt 0 ]; do + if [ "${card#*${BR}${name};*:}" = "${card}" ]; then + printf '%s\n%s;' "${card%${BR}END;:VCARD*}" "${name}" + card=":${BR}END;:VCARD" + break; + else + printf '%s\n%s;' "${card%%${BR}${name};*}" "${name}" + card="${card#*${BR}${name};}" + fi + cnt=$((cnt - 1)) + done + printf '%s:%s\n' "$val" "${card#*:}" +} diff --git a/session_lock.sh b/session_lock.sh new file mode 100644 index 0000000..de1641a --- /dev/null +++ b/session_lock.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +[ "$include_session_lock" ] && return 0 +include_session_lock="$0" + +SLOCK(){ + local file="$1"; + local timeout="${2-900}" + local lockdir="$_DATA/lock/${file#$_DATA}"; lockdir="${lockdir%/}" + local ovlock="${lockdir%/*}/delete.${lockdir##*/}" + local tempfile="$lockdir/${SESSION_ID}" + local lockexpire=$(( $(date +%s) - timeout )) + + mkdir -p "${lockdir%/*}" + + if [ -e "$lockdir" ] \ + && [ "$(stat -c %Y "$lockdir")" -lt "$lockexpire" ] \ + && mkdir "$ovlock"; then + [ "$(stat -c %Y "$lockdir")" -lt "$lockexpire" ] \ + && rm -r "$lockdir" + rmdir "$ovlock" + fi + + printf '%s\n' "$tempfile" + if mkdir "$lockdir" 2>&-; then + cp "$file" "$tempfile" + return 0 + else + return 1 + fi +} + +CHECK_SLOCK(){ + local file="$1"; + local lockdir="$_DATA/lock/${file#$_DATA}"; lockdir="${lockdir%/}" + local tempfile="$lockdir/${SESSION_ID}" + + printf '%s\n' "$tempfile" + if [ -f "$tempfile" ]; then + touch "$lockdir" + return 0 + else + return 1 + fi +} + +RELEASE_SLOCK(){ + local file="$1"; + local lockdir="$_DATA/lock/${file#$_DATA}"; lockdir="${lockdir%/}" + local ovlock="${lockdir%/*}/delete.${lockdir##*/}" + local tempfile="$lockdir/${SESSION_ID}" + + if [ -f "$tempfile" ] && mkdir "$ovlock"; then + [ -f "$tempfile" ] && rm -r "$lockdir" + rmdir "$ovlock" + return 0 + else + return 1 + fi +} diff --git a/shcgi b/shcgi deleted file mode 160000 index 8d60af4..0000000 --- a/shcgi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8d60af41a80ea3760dd51c3bc34eab68d88b16ae diff --git a/static/cards.css b/static/cards.css deleted file mode 100644 index 039bd99..0000000 --- a/static/cards.css +++ /dev/null @@ -1,297 +0,0 @@ -/* -# Copyright 2014 - 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . -*/ - -.filter { - display: block; - border: solid 1px; - border-radius: 4px 4px 0 0; - margin: .5em 2em .25em 2em; - padding: 0 2ex .5em 2ex; - background: #EFF; -} - -.filter h1 { - display: block; - font-weight: bold; - font-size: 1.25em; - border-style: none none solid none; - border-radius: 4px 4px 0 0; - border-width: 1px; - margin: 0 -1.625ex .5em -1.625ex; - padding: .125em 1ex; - background: #EEF; -} -.filter input[type="text"] { - width: 100%; - margin-bottom: .5em; -} -.filter fieldset { - position: relative; - display: block; - border: none; - padding: 0 0 0 12ex; - margin: 0 0 .5em 0; -} -.filter fieldset legend { - position: absolute; - font-size: 1em; - font-weight: bold; - margin-left: -12ex; -} -.filter label { - display: inline-block; - vertical-align: top; - margin: .125em 2ex 0 0; - white-space: nowrap; -} -.filter button { - margin-top: .5em; -} - -.filter fieldset.item { padding-left: 0; } -.filter fieldset.item legend { display: none; } -.filter fieldset.item input[type=text], -.filter fieldset.item fieldset.categories { - background-color: #FFF; - border: 1px solid #000; - margin-top: -1px; - padding: .25em .5em; -} -.filter fieldset.item input[type=radio] { display: none; } -.filter fieldset.item input[type=radio] + label { - position: relative; z-index: 1; - margin: 0; padding: .25em 1em; - -} -.filter fieldset.item input[type=radio]:checked + label { - background-color: #FFF; - border: 1px solid #000; - border-bottom-color: #FFF; -} -.filter fieldset.item input[type=radio][value="CATEGORIES:"]:checked + label + input[type=text], -.filter fieldset.item input[type=radio][value="CATEGORIES:"] + label + input[type=text] + fieldset.categories { - display: none; -} -.filter fieldset.item input[type=radio][value="CATEGORIES:"]:checked + label + input[type=text] + fieldset.categories { - display: block; -} -.filter fieldset.item fieldset.categories a { - font-size: .875em; - line-height: 1.625em; - color: #44C; -} - -.newcard { - display: block; - border: solid 1px; - border-radius: 0 0 4px 4px; - margin: .25em 2em 1em 2em; - padding: .25em 2ex .25em 2ex; - background: #EFF; -} - -.dash { - display: block; - border: solid 1px; - border-radius: 4px 4px 0 0; - margin: .25em 2em .25em 2em; - padding: .25em 2ex .25em 2ex; - background: #EFF; - text-align: right; -} - -.card { - display: block; - position: relative; - border: solid 1px; - margin: .25em 2em; - padding: 0 20ex 0 0; - overflow: auto; - background: #FFF; - min-height: 8em; -} -.card .control { - display: inline-block; - position: absolute; - top: 0; right: 0; - text-align: right; - background: #EEF; - margin: 0; - height: 100%; - width: 20ex; - max-width: 33%; - padding: .25em 1ex; -} -.card .control .item { - display: inline-block; - width: 100%; - min-height: 2.5em; - color: #008; - margin: .25em 0 .5em 0; - border: 1px solid black; - background: #FFF; - padding: .25em .75ex 0 .75ex; - text-decoration: none; - font-size: .75em; -} -.card .control .item:hover{ border-color: #888; } -.card .control input.item { font-size: normal; } -.card .control button.item { text-align: right;} -.card .control button.item:hover{ border-color: #888;} -.card .control .item[name=newfield] { width: 85%;} -.card .control .item[value=addfield] { width: 15%;} - -.card .control #delete + label + button, -.card .control #delete { display: none; } -.card .control #delete + label { background-color: #FEE; } -.card .control #delete:checked + label + button { - position: fixed; display: block; - left: 40%; top: 30%; width: 20%; - font-size: 1.5em; - text-align: center; - background-color: #FAA; - color: #333; - border-radius: .25em 0 0 .25em; -} -.card .control #delete:checked + label:after { - position: fixed; display: block; - left: 60%; top: 30%; width: 5%; - margin: .25em 0; padding: .5em .75ex 0 .75ex; - height: 1.5em; - font-size: 2em; - content: 'X'; - text-align: center; - color: #AAA; background-color: #000; - border-radius: 0 .25em .25em 0; -} - -.card .section { - display: inline-block; - vertical-align: top; - float: left; - width: 20ex; - min-width: 16.5%; - min-height: 6em; - margin: .125em 0 .5ex 0; - padding: 0 1ex .25em 1ex; -} -.card .section:nth-of-type(2n){ background: #EEE;} - -.card .section h2, .card .section h3 { - font-size: 1em; - display: block; - font-weight: bold; - margin: .25em -.5ex .25em -.5ex; - border-style: none none solid none; - border-width: 1px; -} -.card .section .FN { /* will override h2 */ - font-size: 1.2em; - margin-top: 0; -} - -.card .section .item { - display: block; - max-width: 100%; - word-wrap: break-word; - white-space: pre-line; -} -.card .section .item label { - font-weight: bold; -} - -.card .section textarea.NOTE { - min-height: 6em; -} -.card .section textarea.ADR { - min-height: 4em; -} - -.card .section .PHOTO { - width: 100%; - max-height: 10em; -} -.card .section .GENDER { display: inline-block; padding-right: .5ex;} -.card .section .BDAY { display: inline-block;} - -.card .section select { - display: block; - background-color: #FFF; - border: 1px solid black; -} - -.card .section.attendance ul { margin: 0; padding: 0; } -.card .section.attendance ul li { - display: block; - word-wrap: break-word; -} - -.card .section.prescriptions ul { margin: 0; padding: 0; } -.card .section.prescriptions ul li { - display: block; - word-wrap: break-word; - margin: 0 .25ex; -} - -.card .section.prescriptions { width: 40ex;} - -form.card .section input[type=text], -form.card .section input:not([type]), -form.card .section textarea, -form.card .section select {width: 100%;} - -form.card .attendance { - min-width: 66%; -} -form.card .attendance label { - position: relative; - display: inline-block; - width: 25ex; - padding-left: 3ex; -} -form.card .attendance label input[type=checkbox] { - position: absolute; - margin-left: -3ex; -} - -form.card .insurance input[type=radio] {display: none;} -form.card .insurance input[type=radio] + label { - display: inline-block; - width: 50%; - padding: .25ex 0; - text-align: center; - border: 1px solid black; - -} -form.card .insurance input[type=radio]:checked + label { - font-weight: bold; - background-color: #FFF; - border-width: 1px 1px 0 1px; -} -form.card .insurance input[type=radio] + label + input + label + select, -form.card .insurance input[type=radio] + label + select + input {display: none;} -form.card .insurance input[type=radio]:checked + label + input + label + select, -form.card .insurance input[type=radio]:checked + label + select + input { - display: block; - border: 1px solid black; - background-color: #FFF; - border-width: 0 1px 1px 1px; - padding: .25ex 0; - margin-top: -1px; -} diff --git a/static/categories.css b/static/categories.css deleted file mode 100644 index 0a7f41f..0000000 --- a/static/categories.css +++ /dev/null @@ -1,101 +0,0 @@ -/* -# Copyright 2014, 2015, 2018 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . -*/ - -form.categories { - display: block; - border: solid 1px; - border-radius: 4px 4px 0 0; - margin: .5em 2em .25em 2em; - padding: 0 2ex .5em 2ex; - background: #EFF; -} - -form.categories h1 { - display: block; - font-weight: bold; - font-size: 1.25em; - border-style: none none solid none; - border-radius: 4px 4px 0 0; - border-width: 1px; - margin: 0 -1.625ex .5em -1.625ex; - padding: .125em 1ex; - background: #EEF; -} - -form.categories ul { margin: 0; padding: 0; } -form.categories ul li { - display: inline-block; - margin: 0 .25ex; - padding: .125em .25ex .125em 1ex; - border: 1px solid black; - background-color: #EEF; -} -form.categories ul li button { - margin: 0 0 0 1ex; - background-color: #FCC; - border: 1px solid black; - min-width: 3ex; - text-align: center; -} -form.categories ul li:last-of-type { padding: .125em .25ex .125em .25ex; } -form.categories ul li:last-of-type button { background-color: #FFF; } - -form.namelist { - display: block; - margin: 1em 2em; - padding: 0; -} -form.namelist > fieldset { - display: block; - border: solid 1px; - border-radius: 4px 4px 0 0; - margin: 0; padding: .25em 2ex; - background: #EFF; - text-align: right; -} -form.namelist > fieldset:last-of-type { - border-radius: 0 0 4px 4px; -} -ul.namelist { margin: 0; padding: 0;} -ul.namelist > li { - display: block; - border: solid 1px; - margin: -1px 0; - background: #FFF; -} -ul.namelist > li:nth-of-type(2n){ background: #EEE;} - -ul.namelist h2 { - font-size: 1em; - display: inline-block; - margin: 0; - padding: 0 1ex; - min-width: 30ex; -} -ul.namelist ul { margin: 0; padding: 0; display: inline-block;} -ul.namelist ul li { - display: inline-block; - margin: 0 1ex 0 0; -} - -.fakeorder { - position: absolute; - width: 0; height: 0; - left: -100%; -} diff --git a/static/common.css b/static/common.css deleted file mode 100644 index e466cb7..0000000 --- a/static/common.css +++ /dev/null @@ -1,101 +0,0 @@ -* { - font-family: sans-serif; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - z-index: 0; -} -body { - background: #EEE; - margin: 0; - padding: 0; - padding-top: 2.5em; - position: absolute; - min-height: 100%; - width: 100%; - padding-bottom: 2em; -} -.NAVIGATION { - position: fixed; - top: 0; - display: inline-block; - border-style: none solid solid solid; - border-width: 0 1px 1px 1px; - border-radius: 0 0 4px 4px; - margin: 0 2em; - padding: .5em 1ex; - background: #FFF; - z-index: 1; -} -.NAVIGATION label, -.NAVIGATION a { - color: #008; - border: solid 1px #BBF; - margin: .5em .5ex .25em .5ex; - padding: .2em 3ex .2em 3ex; - background: #EFF; -} -.NAVIGATION label:hover, -.NAVIGATION a:hover { - border-width: 2px 1px; - background: #F3FFFF; -} - -#CONFIGURE label[for="navigationconfig"] { display: none; } -#CONFIGURE:target label[for="navigationconfig"] { display: inline; } - -.NAVIGATION input#navigationconfig { display: none; } -.NAVIGATION input + form.config { - position: static; - display: none; -} - -.NAVIGATION .config a { display: block; } -.NAVIGATION .config a:hover { border-width: 1px 2px; } -.NAVIGATION input:checked + form.config { display: block; } -.NAVIGATION .config input[type=text], -.NAVIGATION .config button { - font-size: .875em; - line-height: 1.5em; - height: 1.75em; - padding: 0 .25em; - vertical-align: bottom; - border: 1px solid #000; - background-color: #FFF; -} -.NAVIGATION .config input[type=text] { - margin: .25em 0 0 .375em; - border-right: none; - width: 70%; -} -.NAVIGATION .config button { - min-width: 2.5em; - text-align: center; - max-width: 30%; -} -.NAVIGATION .config button[value=del] { - background: #FCC; -} - - -.trailbtn { display: none; } -.trailbtn + .trailbox { display: none; } -.trailbtn:checked + .trailbox { display: inline-block; } -.trailbtn:checked + .trailbox + .trailbtn { display: block; } -.trailbtn:checked + .trailbox + .trailbtn:before { - display: block; content: '+'; - width: 3ex; text-align: center; - margin-top: .25em; padding: .25em 0; - background-color: #FFF; - border-width: 1px; border-style: solid; -} -.trailbtn:checked + .trailbox + .trailbtn:checked, -.trailbtn:checked { display: none; } - -#footer { - width: 100%; - background-color: #FFF; - border-top: 1px solid #BBF; - margin: 0; padding: .5em 2em; - position: absolute; bottom: 0; -} diff --git a/static/courses.css b/static/courses.css deleted file mode 100644 index a8a8cc3..0000000 --- a/static/courses.css +++ /dev/null @@ -1,195 +0,0 @@ -/* -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . -*/ - -.sort { - display: block; - border: solid 1px; - border-radius: 4px 4px 0 0 ; - margin: .5em 2em .25em 2em; - padding: .25em 2ex .25em 2ex; - background: #EFF; -} -.sort .label { - font-weight: bold; -} - -.newcourse { - display: block; - border: solid 1px; - border-radius: 0 0 4px 4px; - margin: .25em 2em 1em 2em; - padding: .25em 2ex .25em 2ex; - background: #EFF; -} - -.course { - display: block; - position: relative; - border: solid 1px; - margin: .25em 2em; - padding: 0 20ex 0 0; - overflow: auto; - background: #FFF; - min-height: 9em; -} -.course .control { - display: inline-block; - position: absolute; - top 0; right: 0; - text-align: right; - background: #EEF; - margin: 0; - height: 100%; - width: 20ex; - max-width: 33%; - padding: .25em 1ex; -} -.course .control .item { - display: inline-block; - vertical-align: top; - width: 100%; - color: #008; - margin: .25em 0 0 0; - border: 1px solid black; - background: #FFF; -} -.course .control a.item { - border-right: 3px double; - padding: .125em 1ex; -} -.course .control a.item:hover{ - border-right: 1px solid; -} - -.course .control .item[name=newfield] { width: 85%;} -.course .control .item[value=addfield] { width: 15%;} - -.course .section { - display: inline-block; - vertical-align: top; - float: left; - width: 20ex; - min-width: 16.5%; - min-height: 6em; - margin: .125em 0 .5ex 0; - padding: 0 1ex .25em 1ex; -} -.course .section:nth-of-type(2n){ background: #EEE;} - -.course .section .item { - display: block; - max-width: 100%; - word-wrap: break-word; -} - -.course .section h2, .course .section h3 { - font-size: 1em; - display: block; - font-weight: bold; - margin: .25em -.5ex .25em -.5ex; - border-style: none none solid none; - border-width: 1px; -} -.course .section h2 { - font-size: 1.2em; - margin-top: 0; -} - -.course .attendance { - min-width: 66%; -} -.course .attendance a { - display: inline-block; - vertical-align: top; - min-width: 13.5ex; - width: 19%; - color: #008; - border-style: solid; - border-width: 1px; - margin: .25em .125ex .25em .125ex; - padding: .125em 1ex .125em 1ex; - background: #EFF; -} - -.course .dtstart { width: 37ex; } -.course .dtstart .DTSYEAR, -.course .dtstart .DTSMONTH, -.course .dtstart .DTS{ - font-size: .875em; - margin: .25em .25ex 1em .25ex; - display: inline-block; - text-align: center; - font-weight: bold; -} - -.course .dtstart .DTSYEAR{ width: 11.5ex; } -.course .dtstart .DTSMONTH{ width: 18.5ex; } -.course .dtstart .DTS{ width: 5.5ex; } - -.course .dtstart .DTSCAL, -.course .dtstart .DTSCALHEAD { - display: inline-block; - overflow: hidden; - text-align: center; - width: 5ex; - margin: 0; -} -.course .dtstart .DTSCALHEAD { - font-weight: bold; - border-top: 1px solid; - border-bottom: 1px solid; -} - -.course .dtstart input[name=DTSDAY] { display: none; margin: 0; height: 0; width: 0; } -.course .dtstart input[name=DTSDAY]:checked + label.DTSCAL { font-weight: bold; border: solid 1px; } - -.course .dtstart .DTSTIME{ - display: inline-block; - width: 10ex; - font-weight: bold; -} - -.course .recur{ width: 37ex; } -.course .section select, -.course .section button, -.course .section input { - border: 1px solid; - margin: .5em 0; -} - -.course .select_attendance { - max-height: 17em; - overflow-y: scroll; -} -.course .select_attendance label{ - display: block; - position: relative; - margin: 0; padding: 0; - padding-left: 3ex; -} -.course .select_attendance label input{ - position: absolute; - margin: 0; padding: 0; - margin-left: -3ex; - top: 0; -} - -.course .section input[type="text"] { width: 100%; } -.course .section textarea.COMMENT { min-height: 6em; width: 100%; overflow: auto; } - diff --git a/static/email.css b/static/email.css deleted file mode 100644 index e935189..0000000 --- a/static/email.css +++ /dev/null @@ -1,157 +0,0 @@ -/* -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . -*/ - -.filter { - display: block; - width: 128ex; - border-width: 1px; - border-style: solid; - border-radius: 4px 4px 0 0; - margin: .5em auto .25em auto; - padding: .25em 1ex .25em 1ex; - background: #EFF; -} - -.filter > .label { - display: block; - font-weight: bold; - font-size: 1.2em; - border-style: none none solid none; - border-width: 1px; - margin: 0em 0ex .5em 0ex; - background: #EEF; -} - -.filter .search { - margin: 0em 2ex .5em 2ex; -} -.filter .search .label { - display: inline-block; - width: 12ex; - font-weight: bold; - margin-top: .5em; -} -.filter .search input[type="radio"] { - margin-top: .5em; - margin-left: 1ex; -} -.filter .search button { - margin-top: .5em; -} - -.filter .search input[type="text"] { - width: 124ex; -} - -.newcard { - display: block; - width: 126ex; - border-width: 1px; - border-style: solid; - border-radius: 0 0 4px 4px; - margin: .25em auto 1em auto; - padding: .25em 2ex .25em 2ex; - background: #EFF; -} - -.cardlist .card { - display: block; - width: 130ex; - border-style: solid; - border-width: 1px; - margin: .25em auto; - padding: 0; - overflow: auto; - background: #FFF; -} - -.cardlist .card .section { - display: inline-block; - float:left; - width: 20ex; - margin: .125em .25ex .5ex .25ex; - padding: 0 .2em .2em .2em; - background: #EEE; -} -.cardlist .card .section a.attendance { - display: inline-block; - margin-right: 1ex; - word-wrap: break-word; -} - -.cardlist .card .attendance { - width: 83.5ex; -} -.cardlist .card .attendance .check { - display: inline-block; - width: 27ex; -} - -.cardlist .card .control { - float: right; - text-align: right; - margin-right: 0; - background: #EEF; -} -.cardlist .card .control .item { - color: #008; - margin-top: .2em; - margin-right: 1ex; -} -.cardlist .card .control a.item { - min-width: 10ex; - border-style: solid double solid solid; - border-width: 1px 3px 1px 1px; - border-color: #000; - padding: .1em 1ex; - background: #FFF; -} -.cardlist .card .control a.item:hover{ - border-width: 1px 1px 1px 1px; -} - -.cardlist .card .section .sectitle { - display: block; - font-weight: bold; - margin: .2em .2em .2em .2em; -} - -.cardlist .card .section .item { - display: block; - max-width: 20ex; - word-wrap: break-word; -} - -.cardlist .card .section textarea.NOTE { - min-height: 6em; -} -.cardlist .card .section textarea.ADR { - min-height: 4em; -} - -.cardlist .card .section .PHOTO { - width: 20ex; - max-heigth: 30ex; -} - -.cardlist .card .section .FN { - font-weight: bold; - font-size: 1.2em; -} - diff --git a/static/prescriptions.css b/static/prescriptions.css deleted file mode 100644 index 6eebe24..0000000 --- a/static/prescriptions.css +++ /dev/null @@ -1,298 +0,0 @@ -/* -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . -*/ - -@import url("?static=cards.css"); - -body {padding-bottom: 3em; } - -.trailbtn { display: none; } -.trailbtn + .trailbox { display: none; } -.trailbtn:checked + .trailbox { display: inline-block; } -.trailbtn:checked + .trailbox + .trailbtn { display: block; } -.trailbtn:checked + .trailbox + .trailbtn:before { - display: block; content: '+'; - width: 3ex; text-align: center; - margin-top: .25em; padding: .25em 0; - background-color: #FFF; - border-width: 1px; border-style: solid; -} -.trailbtn:checked + .trailbox + .trailbtn:checked, -.trailbtn:checked { display: none; } - -.prescription { - display: inline-block; - width: 96%; max-width: 460px; - color: #800; - background-color: #DDD; - margin: 1em -1% 0 2%; padding: 0; - border: 1px solid #888; - overflow: hidden; - vertical-align: top; -} -form.prescription { padding-top: 1ex;} - -.newprescription { - display: block; - margin: 0 2em; padding: .5ex 2ex; - background-color: #CFF; - border: 1px solid #888; - border-radius: 0 0 1ex 1ex; -} - -.prescription * { - display: inline-block; - font-size: 1em; - line-height: 1em; - margin: 0; padding: 0; -} -.prescription label { - padding-left: .5ex; - font-size: .75em; -} - -.prescription fieldset { - display: inline-block; - margin: 0; padding: 1ex; - margin-right: -.625ex; - border: none; - vertical-align: top; -} -.prescription fieldset br { display: none;} - -.prescription span, -.prescription input, -.prescription textarea { - height: 1.5em; - border: 1px solid #800; - padding: .25ex; - background-color: #FFF; -} -.prescription span { - background-color: #EEE; - padding: .5ex .25ex; - white-space: pre-wrap; - font-size: .75em; - overflow: hidden; -} - -.prescription input[type=checkbox], -.prescription input[type=radio] { display: none;} - -.prescription label.checkbox, -.prescription label.radio, -.prescription input[type=checkbox] + label, -.prescription input[type=radio] + label { padding-left: 1.25em; font-size: 1em;} - -.prescription label.checkbox:before, -.prescription label.radio:before, -.prescription input[type=checkbox] + label:before, -.prescription input[type=radio] + label:before { - display: inline-block; - position: absolute; - margin-left: -1.25em; - width: .75em; height: .75em; - background-color: #FFF; - border: 1px solid #800; - content: ' '; -} -.prescription label.radio:before, -.prescription input[type=radio] + label:before { border-radius: .5em;} -.prescription label.checkbox.checked:before, -.prescription label.radio.checked:before, -.prescription input[type=checkbox]:checked + label:before, -.prescription input[type=radio]:checked + label:before { content: "\2713";} - -.prescription a.button, -.prescription input[type=submit], -.prescription button { - height: 1.5em; - color: #FFF; - background-color: #800; - text-align: center; - text-decoration: none; - margin: 0; padding: .125em 0 0 0; - border: none; -} -.prescription a.button {padding: .5ex;} - -/* ======== Specific ========== */ - -.prescription label.presctype, -.prescription input[name=presctype] + label { - font-size: medium; - width: 22%; - margin: 0; margin-right: -.5ex; - vertical-align: top; - padding: .25em .5ex .25em 3ex; - height: 2.5em; - border-top: 1px solid #DDD; -} -.prescription label.presctype { - text-align: right; - font-weight: bold; - font-size: .875em; - padding-right: 1ex; - padding-left: 0; -} - -input[name=presctype][value\$=private]:checked ~ fieldset, - input[name=presctype][value\$=private] + label, - .prescription.private { background-color: #CFC;} -input[name=presctype][value\$=selfpaid]:checked ~ fieldset, - input[name=presctype][value\$=selfpaid] + label, - .prescription.selfpaid { background-color: #FFC;} -input[name=presctype][value=doctor_compulsory]:checked ~ fieldset, - input[name=presctype][value=doctor_compulsory] + label, - .prescription.doctor.compulsory { background-color: #CFF;} -input[name=presctype][value=dentist_compulsory]:checked ~ fieldset, - input[name=presctype][value=dentist_compulsory] + label, - .prescription.dentist.compulsory { background-color: #FCC;} -input[name=presctype][value^=altpractition]:checked ~ fieldset, - input[name=presctype][value^=altpractition] + label, - .prescription.altpractition { background-color: #FCF;} - -.prescription .baseinfo { width: 60%;} - - .baseinfo label[for=insurance], - .baseinfo #insurance { width: 100%; } - - .baseinfo label[for=name], - .baseinfo #name { width: 65%; margin-right: -.875ex;} - .baseinfo #name { height: 4em; } - - .baseinfo label[for=bday], - .baseinfo #bday { width: 35%;} - .baseinfo #bday { height: 4em; text-align: center; vertical-align: top;} - - .baseinfo label[for=date], - .baseinfo #date { width: 34%; margin-left: 65%;} - .baseinfo #date { text-align: right;} - -.prescription .misc { width: 40%; } - - .misc h1 { - font-size: 1.25em; - font-weight: bold; - width: 100%; - } - .misc label[for=addcontrib], - .misc label[for=contribconfirm] {width: 100%;} - .misc #addcontrib, - .misc #contribconfirm {width: 100%; text-align: right;} - -.prescription label[for=prescreviewed] { - margin-left: 1ex; - font-weight: bold; - text-decoration: underline; - background-color: #FCC; -} -.prescription label[for=prescreviewed].checked, -.prescription :checked + label[for=prescreviewed] { - font-weight: normal; - text-decoration: none; - background-color: transparent; -} - -.prescription .catalogue { width: 100%; } - - .catalogue h2:nth-of-type(1) { - font-weight: bold; - width: 100%; - margin-bottom: .25em; - } - .catalogue label { - display: inline-block; - width: 33%; - margin-right: -.625ex; - margin-top: .25em; - vertical-align: top; - } - .catalogue label[for=presccontinual] { margin-right: 33%;} - -.prescription .description { width: 100%; position: relative;} - .description * { margin-right: -.625ex; vertical-align: top; } - .description label {vertical-align: bottom;} - - .description label[for=quantity] { width: 20%;} - .description label[for=remidy] { width: 60%; } - .description label[for=quantity_weekly] { width: 20%;} - .description #quantity, - .description .quantity { width: 20%; height: 3em; text-align: center;} - .description #remidy, - .description .remidy { width: 60%; height: 3em;} - .description #quantity_weekly, - .description .quantity_weekly { width: 20%; height: 3em; text-align: center;} - - .description .indicator_codes {display: inline-block; width: 20%; padding: 0; padding-top: 1.5ex;} - .description label[for=indicator], - .description label[for=icd10] { display: block; width: 100%;} - .description #icd10, - .description #indicator {width: 100%; text-align: right;} - - .description .indicator_reading { display: inline-block; width: 78%; padding: 0; padding-top: 1.5ex; margin-left: 2%;} - .description label[for=indicator_reading], - .description #indicator_reading { width: 100%; display: block;} - .description #indicator_reading { height: 4em;} - -.prescription .therapy_dates span { min-width: 8em; margin: 0 .5ex;} - -.prescription .issuer { display: inline-block; width: 50%; padding: 0; padding-top: 0; margin-left: 50%;} -.prescription .issuer label:first-of-type { - display: block; - position: relative; - width: 50%; left: -50%; top: 2.25em; - font-size: 1em; - text-align: right; - padding-right: 1ex; - } -.prescription .issuer input[type=radio] + label:before { content: none; } -.prescription .issuer input[type=radio] { display: none; } -.prescription .issuer input[type=radio] + label { - display: inline-block; - width: 50%; - padding: .25ex 0; margin: 0; - text-align: center; - border: 1px solid black; -} -.prescription .issuer input[type=radio]:checked + label { - font-weight: bold; - background-color: #FFF; - border-width: 1px; - border-bottom: 1px solid #FFF; -} -.prescription .issuer input[type=radio] + label + input + label + select, -.prescription .issuer input[type=radio] + label + select + input { display: none; } -.prescription .issuer input[type=radio]:checked + label + input + label + select, -.prescription .issuer input[type=radio]:checked + label + select + input { - display: block; width: 100%; - border: 1px solid black; - background-color: #FFF; - border-width: 0 1px 1px 1px; - padding: .25ex .5ex; - margin-top: -1px; -} -.prescription .issuer input[type=radio]:checked + label + input + label + select option { display: block;} -.prescription span#issuer { width: 100%; height: 3em; padding: 1ex 2ex;} - -.prescription .controls { width: 100%; } - .controls a.button, - .controls button[value=save], - .controls button[value=cancel], - .controls button[value=delete] { width: 25%;} - diff --git a/static/therapy.css b/static/therapy.css deleted file mode 100644 index b17497c..0000000 --- a/static/therapy.css +++ /dev/null @@ -1,360 +0,0 @@ -.trailbtn:checked + fieldset.trailbox { display: block;} - -.trailbtn:checked + .trailbox + .trailbtn { - display: block; - height: 2.25em; padding: 0 3ex; - font-size: 1em; font-weight: normal; - color: #000; background-color: #FDD; - border: 1px solid #000; - border-radius: 4px; -} -.trailbtn:checked + .trailbox + .trailbtn[type=submit]:before {content: none;} - - -* { box-sizing: border-box; } -body { - overflow: scroll; - position: relative; - width: 100%; - margin: 0; padding: 0; - padding-top: 2em; -} - -form > button[type=submit] { - position: fixed; display: block; - top: 0; right: 2.5em; - height: 2.25em; padding: 0 3ex; - font-size: 1em; font-weight: bold; - color: #000; background-color: #FDD; - border-width: 1px; border-color: #000; - border-style: none solid solid solid; - border-radius: 0 0 4px 4px; - z-index: 3; -} -form > button[type=submit]:hover { - background-color: #FEE; -} - -input.tab { display: none; } -input.tab + label.tab { display: block; } -input.tab + label.tab::before { content: '\25b8 \00a0'; float: left;} -input.tab:checked + label.tab::before { content: '\25be \00a0'; } -input.tab + label.tab + div.tab { display: none; } -input.tab:checked + label.tab + div.tab { display: block; } - -input.color { display: none } -input.color + label{ - display: inline-block; - width: 1em; height: 1em; - border: 1px solid black; -} -input.color:checked + label{ border-width: 3px;} -input.color[value="#000"] + label, -input.color[value="#888"] + label { background-color: #888;} -input.color[value="#00A"] + label { background-color: #00F;} -input.color[value="#0A0"] + label { background-color: #0F0;} -input.color[value="#0AA"] + label { background-color: #0FF;} -input.color[value="#A00"] + label { background-color: #F00;} -input.color[value="#A0A"] + label { background-color: #F0F;} -input.color[value="#AA0"] + label { background-color: #FF0;} -input.color[value="#FFF"] + label { background-color: #FFF;} - -h1, label.tab, div.tab, fieldset.tab, -div.patient, div.prescription { - display: block; - width: 96%; - margin: 0 2%; -} - -div.prescription span { - display: inline-block; - width: 50%; - margin-right: -.75ex; - vertical-align: top; -} -div.prescription span label { font-weight: bold; margin-right: 1ex;} -div.prescription span.prescno, -div.prescription span.catalogue { - width: 33%; - font-weight: bold; - margin-bottom: .5em; - padding: .5ex 1ex; -} - -div.prescription ul {margin-top: 0;} - -div.prescription label.checkbox, -div.prescription label.radio { - display: block; - padding-left: 1.25em; - font-size: 1em; - margin: .5em 0; -} - -div.prescription label.checkbox:before, -div.prescription label.radio:before { - display: inline-block; - color: #000; - background-color: #FFF; - height: 1.375em; width: 1.125em; - padding: .125em 0 0 .375em; - margin: 0 .5em .25em -1.25em; - border: 1px solid #000; - vertical-align: middle; - content: ' '; -} -div.prescription label.radio:before { border-radius: .5em;} -div.prescription label.checkbox.checked:before, -div.prescription label.radio.checked:before { content: "\2713";} - -div.prescription label[for=prescreviewed] { - margin-left: 1ex; - font-weight: bold; - text-decoration: underline; - background-color: #FCC; -} -div.prescription label[for=prescreviewed].checked { - font-weight: normal; - text-decoration: none; - background-color: transparent; -} - -div.prescription label.tab {width: 96%; border: none; border-bottom: 1px dotted;} -div.prescription div.tab { width: 96%; background-color: #DDD;} - -input.stickynote { display:none; } -input.stickynote + .stickynote { - position: fixed; - background-color: #FF8; - top: 4em; bottom: 4em; - left: -4.5em; width: 5em; - padding: 1ex; - max-height: 90%; - z-index: 2; -} -input.stickynote + .stickynote:nth-of-type(2n) { - background-color: #8FF; - top: 8em; -} - -input.stickynote + .stickynote > * { display: none; } -input.stickynote + .stickynote > label { - position: absolute; - top: 0; bottom: 0; right: .5ex; - display: block; - text-align: right; - font-weight: bold; -} -input.stickynote + .stickynote:hover { - left: -1ex; -} -input.stickynote:checked + .stickynote { - width: auto; left: 1em; right: 1em; -} -input.stickynote:checked + .stickynote > * { display: block; } -input.stickynote:checked + .stickynote > textarea { - display: block; - position: absolute; - left; 0; right: 0; bottom: 0; top: 0; - width: 100%; height: 100%; - background-color: #FF8; - padding: 2em 1em; -} -input.stickynote:checked + .stickynote > button[type="submit"] { - display: block; - position: absolute; - right: .5ex; bottom: .5ex; - z-index: 2; -} -input.stickynote:checked + .stickynote > label { - display: block; - position: static; - font-size: 0; -} -input.stickynote:checked + .stickynote > label:before { - position: absolute; - font-size: initial; - content: "x"; - top: .5ex; right: .5ex; - padding: .125ex .75ex; - background-color: #000; - color: #FFF; - border-radius: 1ex; - z-index: 2; -} - -fieldset.penwidth, -fieldset.color { - position: absolute; - right: 0; width: 2em; - margin: .5em .5em .125em 2%; - border: none; - padding: 0; -} -fieldset.penwidth { bottom: 19em; } -fieldset.penwidth > input {display: none;} -fieldset.penwidth > input + label { display: none;} -fieldset.penwidth > input:checked + label { - display: block; - width: 2em; height: 2em; - background-color: #000; - border: 1em solid #FFF; - border-radius: 1em; -} -fieldset.penwidth > input[value="4"] + label { border-width: .75em; } -fieldset.penwidth > input[value="12"] + label { border-width: .5em; } -fieldset.penwidth > input[value="36"] + label { border-width: .25em; } - -fieldset.color { bottom: 0; } -fieldset.color > input.color + label { - width: 2em; height: 2em; -} -.dotmark { - max-width: 90%; - margin: .5em 1em .125em 2%; padding: 0; - text-align: left; - border: 1px solid black; -} -.dotmark.ov { - position: absolute; - left: 0; bottom: .25em; - z-index: 1; -} - -@media(min-width: 800px){ - h1, label.tab, div.tab, fieldset.tab, - div.patient, div.prescription { - width: 38%; - margin-right: 0; - } - input.stickynote:checked + .stickynote { right: 50%; } - fieldset.penwidth, - fieldset.color { position: fixed; } - .dotmark { - position: fixed; - max-width: 52%; - max-height: 98%; - right: 2em; bottom: .25em; - } - .dotmark.ov { - position: fixed; - right: 2em; left: auto; - } -} - -h1 {display: none;} - -div.patient, div.prescription { margin-top: 1em; } -div.prescription { - border: 1px solid black; - background-color: #EEE; - padding: .125em 1.25ex .5em 1.25ex; -} -div > h2 { margin: 0; border-bottom: 1px solid black; } -div:nth-child(n+2) > a:first-of-type { - display: block; - margin: .125em 0 .5em 0; - text-decoration: none; -} - -#report fieldset.tab, -#report label.tab { - font-size: 1.25em; - font-weight: bold; - padding: .125em 1ex .25em 1ex; - color: #FFF; - background-color: #333; - margin-top: .125em; - text-align: right; - border: none; -} -#report label.heading { - background-color: #FFF; - margin-top: 1em; - border: 2px solid black; - border-bottom-width: 1px; - color: black; -} -#report label.heading > span { - text-decoration: underline; -} - -#report fieldset.tab > *, -#report label > input, -#report label > span { - display: inline-block; - text-align: right; -} -#report .tab > .no { - width: 10%; float: left; - border: solid 1px #FFF; - background-color: #555; - border-radius: 2ex; - padding: 0; - text-align: center; -} -#report label.heading > span.no { - background-color: inherit; - border: none; -} -#report .tab > .date { width: 30%; } -#report .tab > .therapist { width: 30%; } -#report .tab > .signature { width: 20%; } -#report label.tab > .signature { font-size: .75em; } - -#report .signature > input[type=checkbox] { - display: inline; - font-weight: bold; - font-size: 1.25em; -} -#report .signature > input[type=checkbox]:before { - display: block; width: 1.25em; - margin: -.125em 0 0 -.5ex; - background-color: #FFF; - text-align: center; - content: "\00a0 \00a0 \00a0"; -} -#report .signature > input[type=checkbox]:checked::before { - content: "\2713"; -} - -#report input.tab + label.tab > input.date, -#report input.tab + label.tab > input.therapist { - display: none; -} -#report input.tab:checked + label.tab > input.date, -#report input.tab:checked + label.tab > input.therapist { - display: inline; -} -#report input.tab:checked + label.tab > span.date, -#report input.tab:checked + label.tab > span.therapist { - display: none; -} - -#report div.tab { - border: 2px solid #333; - border-top-width: 1px; - margin-top: -1px; - padding: .25em .5ex 1em .5ex; -} -#report div.tab > fieldset.note { - border: none; - margin: 0; padding: 0; -} -#report div.tab > fieldset.note > textarea { - display: block; - width: 93%; height: 8em; - margin: -8em 0 .5em 2em; - font: normal 1em sans-serif; -} -div.tab > fieldset.note > input.color + label { margin: 0; display: block; } -div.tab > fieldset.note > input.color[value="#888"]:checked ~ textarea { background-color: #AAA; } -div.tab > fieldset.note > input.color[value="#00A"]:checked ~ textarea { background-color: #88F; } -div.tab > fieldset.note > input.color[value="#0A0"]:checked ~ textarea { background-color: #8F8; } -div.tab > fieldset.note > input.color[value="#0AA"]:checked ~ textarea { background-color: #8FF; } -div.tab > fieldset.note > input.color[value="#A00"]:checked ~ textarea { background-color: #F88; } -div.tab > fieldset.note > input.color[value="#A0A"]:checked ~ textarea { background-color: #F8F; } -div.tab > fieldset.note > input.color[value="#AA0"]:checked ~ textarea { background-color: #FF8; } -div.tab > fieldset.note > input.color[value="#FFF"]:checked ~ textarea { background-color: #FFF; } - -div.tab > button.delete {float: right; display: inline-block; margin-top: -1em; display: none;} diff --git a/static/therapy_background.png b/static/therapy_background.png deleted file mode 100644 index a0574d5..0000000 Binary files a/static/therapy_background.png and /dev/null differ diff --git a/static/therapy_background.xcf b/static/therapy_background.xcf deleted file mode 100644 index e69c024..0000000 Binary files a/static/therapy_background.xcf and /dev/null differ diff --git a/static/therapy_draw.js b/static/therapy_draw.js deleted file mode 100644 index 8a8e936..0000000 --- a/static/therapy_draw.js +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2016 Paul Hänsch -// -// This file is part of Confetti. -// -// Confetti is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Confetti is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with Confetti. If not, see . - -body = document.body -dbg = document.getElementById("jsdebug") -canvas = document.getElementById("canvas") -data=document.getElementById("image_serialize") - -image = canvas.getContext("2d") -mouse = 0 -image_serialize="" - -// start and current coordinates of a draw -// serves for tracking, whether path ends close to its beginning -stx=0, sty=0 -cux=0, cuy=0 - -function setstroke(w) { - image.lineWidth = w - data.value += " stroke-width " + image.lineWidth -} -function setcol(c) { - this.c = c - image.strokeStyle = c - image.fillStyle = c - data.value += " stroke " + c + "F" -} - -function relX(x){ - if ( body.clientWidth >= 800 ){ - return Math.floor(cscaleW * (x - canvas.offsetLeft)) - } else { - return Math.floor(cscaleW * (x - canvas.offsetLeft + window.pageXOffset)) - } -} -function relY(y){ - if ( body.clientWidth >= 800 ){ - return Math.floor(cscaleH * (y - canvas.offsetTop)) - } else { - return Math.floor(cscaleH * (y - canvas.offsetTop + window.pageYOffset)) - } -} - -function draw(x, y) { - if ( mouse == 1){ - cux=relX(x), cuy=relY(y) - - image.lineTo( cux, cuy ) - image.stroke() - - image_serialize += " " + cux + "," + cuy - } -} - -function drawstart(x, y) { - mouse = 1 - - cscaleW = canvas.width / canvas.clientWidth - cscaleH = canvas.height / canvas.clientHeight - - stx=relX(x), sty=relY(y) - - setstroke(document.querySelector('input[name="penwidth"]:checked').value); - setcol(document.querySelector('input[name="color"]:checked').value); - - image.beginPath() - draw(x, y) // why must this not use relative Coords ??? - - image_serialize = " polyline" -} - -function drawstop() { - - // if path ends close to beginning ( < 50 px); then close path and fill - if ( false && mouse == 1 && Math.sqrt( Math.pow(stx - cux, 2) + Math.pow(sty - cuy, 2)) <= 50 && c !== "#FFF" ){ - image.lineTo( stx, sty ) - image.stroke() - - image.globalAlpha = .5 - image.fill() - image.globalAlpha = 1 - - image_serialize += " " + stx + "," + sty - data.value += " fill " + c + "8" + image_serialize - } else if (mouse == 1) { - data.value += " fill #0000 " + image_serialize - } - dbg.innerHTML = " stx: " + stx + " cux: " + cux + " sty: " + sty + " cuy: " + cuy - - image.closePath() - image_serialize = "" - mouse = 0 -} - -window.addEventListener( 'mouseup', function() { drawstop() } ) -canvas.addEventListener( 'mousedown', function(e) { drawstart(e.clientX, e.clientY) } ) -canvas.addEventListener( 'mousemove', function(e) { draw(e.clientX, e.clientY) } ) - -window.addEventListener( 'touchend', function() { drawstop() } ) -canvas.addEventListener( 'touchstart', function(e) { drawstart(e.touches[0].clientX, e.touches[0].clientY) } ) -canvas.addEventListener( 'touchmove', function(e) { e.preventDefault(); draw(e.touches[0].clientX, e.touches[0].clientY) } ) diff --git a/style.css b/style.css new file mode 100644 index 0000000..06aacdd --- /dev/null +++ b/style.css @@ -0,0 +1,456 @@ +* { + position: relative; + box-sizing: border-box; + max-width: 100%; + font-family: sans-serif; + font-weight: normal; + font-size: initial; + font-style: normal; + text-decoration: none; + line-height: 1.5em; + color: inherit; background: transparent; + padding: 0; margin: 0; + border: none; +} + +body { + color: #000; background-color: #FFF; +} + +/* ======= GENERIC HTML STYLES =======*/ + +p { margin-bottom: 1em; } + +a { + text-decoration: underline; + font-style: italic; + color: #068; +} + +i, em { font-style: italic; } +b, strong { font-weight: bolder; } +ul, ol { margin-left: 1.125em; } +table th { font-weight: bold; } + +h1, h2, h3 { + font-weight: bold; + margin-top: 1em; + margin-bottom: .5em; +} + +h1:first-child, h2:first-child, h3:first-child, +p + h1, p + h2, p + h3 { + margin-top: 0; +} + +h4, h5, h6, form legend { + font-weight: bolder; + margin-bottom: .25em; +} + +h1 { font-size: 1.5em; } +h2 { font-size: 1.125em; } + +select, input, button, textarea, +.control a { + display: inline-block; + background-color: #FFF; + border: 1pt solid; + padding: .25em .75em; + vertical-align: text-bottom; +} +.control a { + color: inherit; + font-style: normal; + text-decoration: none; +} + +select { padding: .375em 0; } + +input[type=radio], input[type=checkbox] { + vertical-align: baseline; +} +input[type=number] { text-align: right; padding-right: 0; } + +button, input[type=button], +.control a { + box-shadow: .125em .125em .25em; + cursor: pointer; +} +input[type=radio], input[type=checkbox], +input[type=radio] + label, input[type=checkbox] + label { + cursor: pointer; +} + +label { margin-right: .75em; } +input + label { + margin-left: .375em; +} + +/* ====== COMMON ELEMENTS ======*/ + +body > ul.menu { + display: block; + height: 1.75em; + margin: 0; -padding: 0 .5em; + list-style: none; + color: #CCC; + background-color: #444; + box-shadow: inset 0 -.25em .25em #000; + overflow: hidden; + z-index: 3; +} + +body > .menu li { + display: inline-block; +} +body > .menu a { + color: inherit; + padding: .5em 3em; + box-shadow: inset 0 0 .5em #000; +} +body.cards > .menu a[href="/cards/"], +body.courses > .menu a[href="/courses/"] { + color: #000; + background-color: #FFF; + box-shadow: none; +} + +/* =========== FILTER AND SEARCH Headers ========= */ + +form.categories, +form.search, form.sort, form.filter, form.newcard, form.newcourses { + margin-top: 1em; padding: 0 1em; + z-index: 1; +} +form.filter > h1 { display: none; } + +form.filter fieldset { margin-top: .5em; } +form.filter fieldset.item + fieldset.item legend { display: none; } +form.filter fieldset.item input[type=radio] { display: none; } +form.filter fieldset.item input[type=radio] + label { + display: table-cell; + padding: .5em 1em; + background-color: #EEE; + border-style: solid; + border-width: .5pt .25pt 0 .25pt; + white-space: normal; +} +form.filter fieldset.item input[type=radio] + label:first-of-type { + border-left: 1pt solid; +} +form.filter fieldset.item input[type=radio]:checked + label { + position: relative; + background-color: #FFF; + box-shadow: .125em -.125em .125em #888; + z-index: 1; +} +form.filter fieldset.item input[type=text], +form.filter fieldset.item fieldset.courses, +form.filter fieldset.item fieldset.categories { + position: relative; + display: block; + width: 100%; + margin-top: -1pt; + padding: .25em .75em; + border: 1pt solid; + box-shadow: .125em .125em .25em #888; +} +form.filter fieldset.item fieldset.courses, +form.filter fieldset.item fieldset.categories { display: none; } +form.filter fieldset.item input[value=course]:checked ~ input[type=text], +form.filter fieldset.item input[value=CATEGORIES]:checked ~ input[type=text] { display: none; } +form.filter fieldset.item input[value=course]:checked ~ fieldset.courses, +form.filter fieldset.item input[value=CATEGORIES]:checked ~ fieldset.categories { display: block; } + +form.filter fieldset.order legend { + float: left; margin-right: 1em; +} + +form.filter fieldset label, +form.filter fieldset a { white-space: pre;} +form.filter button[type=submit] { + margin-top: .5em; margin-bottom: .5em; +} + +form.filter button[value=export_csv] { margin-left: 1em; } + +body.courses form .order { display: inline-block; margin-right: 2em;} + +body.cards form.newcard { display: flex; } +body.cards form.newcard input[name=seed] { flex: 1; } + + +/* ============ LIST ITEMS, Generic ============= */ + +body > form, +div.card, +div.course { + position: relative; + width: 98%; width: calc(100% - 2em); + margin-left: auto; margin-right: auto; + margin-bottom: 1em; + box-shadow: .125em .125em .25em; + z-index: 1; +} + +/* HACK: put anchor point 10em above card and highlight target element */ + div:target { box-shadow: none; z-index: 0; } + div:target:before { + content: ''; + display: block; + margin-top: -10em; + height: 10em; + visibility: hidden; + } + div:target:after { + content: ''; + display: block; + position: absolute; + left: 0; right: 0; + top: 10em; bottom: 0; + box-shadow: .125em .125em .25em; + animation: highlight 4s; + z-index: -1; + } + @keyframes highlight { from { background-color: #FF0; } to { background-color: transparent; } } +/**/ + +div .section, form .section { + display: block; + vertical-align: top; + padding: 0 1em; + overflow: hidden; + word-break: break-word; +} + +div .section :last-child, form .section :last-child { + margin-bottom: 1em; +} + +div .section h2, form .section h2, +div .section h3, form .section h3 { + border-bottom: 1pt solid #EEE; +} +div .control, form .control { + background-color: #EEE; + padding: .25em; + text-align: right; +} + +@media(min-width: 60em) { + div .section, form .section { + display: table-cell; + width: calc(100% / 10); + } + div .section :last-child { margin-bottom: 0; } + div .control, form .control { + background-color: transparent; + } + div .section:nth-of-type(2n) { + background-color: #EEE; + } +} +@media(min-width: 80em) { + div .control, form .control { + display: table-cell; + width: calc(100% / 10); + } + div .control .item, form .control .item { + display: block; + margin-bottom: .25em; + } +} + +div .section .item, form .section .item, +form .section.attendance > label { + display: block; + width: 100%; +} + +div .section .item.NOTE { + white-space: pre-wrap; +} + +form .section .item { + margin-bottom: .25em; +} + +form .section button[value^=addfield] { + font-size: .75em; + margin-top: .5em; padding: 0 .375em; +} + +/* HACK: "responsive" Delete Button above each field */ + form input.delete { display: none; } + form input.delete + label { + float: right; + font-size: .75em; + line-height: 1; + max-width: 1.75em; height: 1.125em; overflow: hidden; + color: #FBB; background-color: #444; + margin: 0; padding: .125em .5em 0 .5em; + border-radius: 4pt 4pt 0 0; + transition: max-width .3s; + } + form input.delete + label:before { content: '\274c '; margin-right: .5em; } + form input.delete + label:hover { max-width: 10em; } + -form input.delete + label:hover:before { content: ''; } + -form input.delete + label:hover:after { content: ' \274c'; } + form input.delete:checked + label, + form input.delete:checked + label + *, + form input.delete:checked + label + .teltype + .TEL { + display: none; + } +/**/ + + +/* ====== right hand Control Buttons on list items ====== */ + +form .control { + position: relative; + padding-left: 11em; + padding-top: 1.5em; +} +form .control .item { + display: inline-block; + margin-bottom: .25em; + vertical-align: text-bottom; +} + +/* Combined Select/Submit Box */ + form .control .item.newfield { box-shadow: .125em .125em .25em; } + form .control .item.newfield select { margin-right: -1pt; } + form .control .item.newfield button { box-shadow: none; } +/**/ + +/* HACK: Delete Checkbox before delete Button */ + form .control .item.delete { + position: absolute; + bottom: .375em; left: .25em; width: auto; + padding-bottom: calc(2.25em + 2pt); + } + + form .control .item.delete input + label + button { + display: none; + position: absolute; + bottom: 0; width: 100%; + color: #800; + background-color: #FEE; + z-index: 1; + } + form .control .item.delete:after { + content: attr(label); + display: block; + position: absolute; + bottom: 0; width: 100%; + text-align: center; + color: #BAA; + padding: .25em 0; + border: 1pt solid; + box-shadow: .125em .125em .25em; + } + form .control .item.delete input:checked + label + button { display: block; } +/**/ + +@media(min-width: 80em) { + form .control { padding: .25em; min-height: 16em; } + form .control .item { width: 100%; } + form .control .item.newfield select { width: calc(100% - 2.5em); } + form .control .item.delete { bottom: .125em; right: .25em; } +} + +/* ======= LIST ITEMS, Courses ======= */ + +form.course .dtstart input[name=DTS_YEAR], +form.course .dtstart select[name=DTS_MONTH] { width: calc(50% - 1.25em); } +form.course .dtstart input[name=DTS_YEAR] { margin-right: -.375em; } +form.course .dtstart table { width: 100%; margin: 1em 0; } +form.course .dtstart table td { text-align: right; -border: .5pt solid; } +form.course .dtstart table input[type=radio] { display: none; } +form.course .dtstart table input[type=radio] + label { + display: block; + width: 100%; + margin: 0; padding: 0 3pt; +} +form.course .dtstart table input[type=radio]:checked + label { + font-weight: bold; + padding: 0 2pt; + box-shadow: .125em .125em .25em; +} + +form.course .dtstart label.DTSTIME { + display: inline-block; + font-weight: bold; + margin: 0; + width: calc(100% - 7.875em); +} +form.course .dtstart input[name=DTS_HOUR], +form.course .dtstart input[name=DTS_MINUTE] { + vertical-align: baseline; + width: 3.5em; + margin-bottom: 0; +} + +form.course .recur .item { white-space: nowrap; } +form.course .recur .item > * { margin-bottom: 0; vertical-align: baseline; } +form.course .recur input[name=RRULE_INTERVAL], +form.course .recur input[name=RRULE_COUNT], +form.course .recur input[name=RRULE_UMONTH], +form.course .recur input[name=RRULE_UDAY] { width: 3.5em; } +form.course .recur input[name=RRULE_UYEAR] { width: 4.5em; } +form.course .recur input[name=RRULE_UYEAR], +form.course .recur input[name=RRULE_UMONTH], +form.course .recur input[name=RRULE_UDAY] { + margin-right: -.375em; +} + +form.course .attendance div.attendance { + max-height: 16em; + overflow-y: scroll; +} +form.course .attendance label { + display: inline-block; + max-width: calc(100% - 2em); + vertical-align: top; + margin-bottom: 0; +} +form.course .attendance input { margin-top: .375em; } + +/* ======== Categories Page ======== */ + +body.categories form ul { list-style: none; margin: 0; } + +form.categories li { + display: inline-block; + background-color: #EEE; + margin-right: .5em; margin-bottom: .5em; + padding-left: .5em; + box-shadow: .125em .125em .25em; +} +form.categories li button[name=remove] { + font-size: .75em; + width: 2.5em; + background-color: #FBB; + overflow: hidden; + white-space: pre; +} +form.categories li button[name=remove]:before { + content: '\274C '; + margin-right: 3em; +} + +form.categories li:last-child { padding-left: 0 } + +body.categories form.namelist ul.namelist > li:nth-of-type(2n + 1) { background-color: #EEE; } +body.categories form.namelist ul.namelist > li h2, +body.categories form.namelist ul.namelist > li ul { + display: inline-block; +} +body.categories form.namelist ul.namelist > li h2 { + width: 20%; + min-width: 10em; +} +body.categories form.namelist ul.namelist > li ul li { + display: inline-block; +} diff --git a/templates/cards.html.sh b/templates/cards.html.sh deleted file mode 100755 index e486317..0000000 --- a/templates/cards.html.sh +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright 2014 - 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -setchecked() { - printf 'checked="checked"' -} -check_type(){ - [ "${_GET[filter]%%:*}" = "$1" ] && setchecked -} -check_order(){ - [ "${_GET[order]}" = "$1" ] && setchecked -} - -filter_item() { -cat < - $(l10n filter_item): - - - - - - - - - - - - - - - - - - - - - - - - - -
- $(m=3 - list_categories \ - | while read cat; do - printf '' \ - "$((n + m))" "$(attribsafe "$cat")" "$(printf %s "$cat" |grep -qEx "$2" && setchecked)" "$(htmlsafe "$cat")" - m=$((m + 1)) - done - ) - $(l10n edit_categories) -
- - - -EOF -} - -cat < -

$(l10n filter_label)

- - - $( - n=0; m="$(list_categories |wc -l)" - printf '%s\n' "${_GET[filter]}" |tr '^' '\n' \ - | while read filter; do - [ -n "$filter" ] && filter_item "${filter%%:*}" "${filter#*:}" "$n" - n=$((n + 3 + m)) - done - filter_item any '' "$n" - ) - -
- $(l10n filter_order): - - - -
- - - - -
- -
- -${edit:+$(edit_card "$edit")} - - -EOF - -listcards |grep ${edit:+-v} "$edit" \ -| while read card; do - cat <<-ENDCARD -
- $(view_card "$card")
- $(l10n edit) - $(l10n vcf_export) - ${profile_medical:+ - $(l10n new_prescription) - } -
-
- ENDCARD -done - -# vi:set filetype=html: diff --git a/templates/categories.html.sh b/templates/categories.html.sh deleted file mode 100755 index 75223bb..0000000 --- a/templates/categories.html.sh +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2015, 2017, 2018 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -cat_listing(){ - list_categories | while read cat; do - printf '
  • %s
  • \n' \ - "$(htmlsafe "$cat")" "$(attribsafe "$cat")" "$(l10n cat_remove)" - done -} - -list_catsel(){ - card="$1" - cats="$(get_categories "$card")" - - list_categories |while read cat; do - printf '
  • ' \ - "$(printf %s "$cats" |grep -qF "$cat" && printf 'checked="checked"')" \ - "$(attribsafe "$card")" "$(attribsafe "$cat")" "$(htmlsafe "$cat")" - done -} - -display_catsel(){ - card="$1" - printf '
  • %s

      ' "$(htmlsafe "$(get_name "$card")")" - list_catsel "$card" - printf '
  • ' -} - -cat < - -

    $(l10n categories_label)

    - -
      - $(cat_listing) -
    • - - -
    • -
    - - -
    -
    - -
    -
      -EOF -listcards \ -| while read card; do - display_catsel "$card" -done -cat < -
      - -
      - -EOF - diff --git a/templates/course_print.sh b/templates/course_print.sh deleted file mode 100755 index 72ab8b8..0000000 --- a/templates/course_print.sh +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2014 - 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -. ${_EXEC}/templates/text_cards.sh - -echo -E ' -\documentclass[landscape,10pt]{article} -\usepackage[utf8x]{inputenc} -\usepackage{ngerman} -\usepackage{eurosym} -\usepackage[landscape,margin=0.25in]{geometry} -\usepackage{longtable} - -\begin{document} - -\section*{Teilnehmende} -\begin{longtable}{|p{60mm}|l|p{50mm}|p{80mm}|} -\hline - \textbf{'"$(l10n N)"'} & - \textbf{'"$(l10n BDAY)"'} & - \textbf{'"$(l10n TEL)"'} & - \textbf{'"$(l10n NOTE)"'} \\ -\hline -\hline -\endhead -'"$( -list_attendance "$course" |sort -k 2 |while read line; do - cardfile="$(echo "$line" |cut -d\ -f1)" - list_attendee "$cardfile" |sed -r 's:$:\\\\[3ex] \\hline:' -done -)"' -\end{longtable} - -\newpage - -\section*{Termine} -\begin{longtable}{|p{60mm}|c|c|c|c|c|c|c|c|c|c|} -\hline - '"$(get_dates)"' \\ -\hline -\hline -\endhead -'"$( -tex_clean "$(list_attendance "$course")" |sort -k 2 | debug |sed -r 's:^[0-9a-z\.]+ (.+) \(\*[0-9]{4}\)$:\1:;s:$: \& \& \& \& \& \& \& \& \& \& \\\\[3ex] \\hline:' -)"' -\end{longtable} - -\end{document} -' diff --git a/templates/courses.html.sh b/templates/courses.html.sh deleted file mode 100755 index 99e3381..0000000 --- a/templates/courses.html.sh +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -edit="${_GET[edit]}" - -check_order(){ - [ "${_GET[order]}" = "$1" ] && echo 'checked="checked"' -} - -cat < - - - -
      -
      - -
      -
      - -
      -$( -[ -f "ical/$edit" -o -f "temp/$edit" ] && edit_course "$edit" -if [ "$?" = 0 ]; then - listcourses |grep -v "$edit" -else - listcourses -fi |while read card; do - view_course "$card" -done -) -
      -EOF - -# vi:set filetype=html: diff --git a/templates/edit_card.sh b/templates/edit_card.sh deleted file mode 100755 index bc71db2..0000000 --- a/templates/edit_card.sh +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright 2014 - 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -setchecked() { - printf 'checked="checked"' -} -setselected() { - printf 'selected="selected"' -} -check_gen() { - [ "$values[GENDER]" = "$1" ] && setselected -} -check_a() { - egrep -q "^${1}.${id}$" "$_DATA/mappings/attendance" && setchecked -} -check_c() { - for n in CATEGORIES CATEGORIES{0..100}; do - if [ -n "${values[$n]+x}" ]; then - [ "${values[$n]}" = "$1" ] && setchecked && break - else - break - fi - done -} - -teltype() { - cat <<-EOF - - EOF -} - -list_items(){ - item="$1" - placeholder="$2" - [ -n "${values[$item]+x}" ] && printf '

      %s

      \n' "$(l10n $item)" - for n in "$item" "$item"{0..100}; do - if [ -n "${values[$n]+x}" ]; then - case "$item" in - (ADR|NOTE) - printf '\n' \ - "$item" "$item" "$(htmlsafe ${values[$n]})" - ;; - (TEL) - teltype "${values[${n}_TYPE]}" - printf '\n' \ - "$item" "$item" "$(attribsafe ${values[$n]})" - ;; - (*) - printf '\n' \ - "$item" "$item" "$(attribsafe ${values[$n]})" "$placeholder" - ;; - esac - else - break - fi - done -} - -list_section(){ - printf '
      ' "$1" - shift 1 - for each in $@; do - list_items "$each" - done - printf '
      ' -} - -hi_company="${values[X-HEALTH-INSURANCE+0]}" - hi_number="${values[X-HEALTH-INSURANCE+1]}" - hi_status="${values[X-HEALTH-INSURANCE+2]}" - -[ -z "$values[UID]" ] && values[UID]="$(uuidgenerator)" - -cat < - -
      -

      $(l10n N)

      - - - - - - - -END_HTML - - for n in NICKNAME NICKNAME{0..100}; do - [ -z "${values[$n]+x}" ] && break \ - || printf ' - - ' "$(attribsafe ${values[$n]})" "$(l10n NICKNAME)" - done - - list_items BDAY YYYY-MM-DD - list_items X-ZACK-JOINDATE YYYY-MM-DD - list_items X-ZACK-LEAVEDATE YYYY-MM-DD - - [ -n "$values[SOUND]" ] && printf ' - ' "$values[SOUND]" - - [ -n "$values[PHOTO]" ] && printf ' - - ' "${values[PHOTO_TYPE]}" "${values[PHOTO]}" - - [ -n "$values[LOGO]" ] && printf ' - - ' "${values[LOGO_TYPE]}" "${values[LOGO]}" - -printf '
      ' - -if [ "$PROFILE" = circus ]; then - list_section phone TEL - list_section message EMAIL IMPP URL - list_section address ADR - list_section note NOTE X-CLIENT-REFERRAL - cat <<-END_HTML -
      -

      $(l10n course_attendance)

      - $(listcourses |while read each; do - cname="$(sed -rn 's:^SUMMARY\:(.*)$:\1:p' "$_DATA/ical/$each")" - printf '' \ - "$(attribsafe "$each")" "$(check_a "$each")" "$(htmlsafe "$cname")" - done) -

      $(l10n CATEGORIES)

      - $(list_categories |while read each; do - printf '' \ - "$(attribsafe "$each")" "$(check_c "$each")" "$(htmlsafe "$each")" - done) -
      - END_HTML -elif [ "$PROFILE" = medical ]; then - list_section address ADR - list_section phone TEL EMAIL IMPP URL - cat <<-END_HTML -
      -

      $(l10n X-HEALTH-INSURANCE)

      - - - - - -
      - END_HTML - list_section note NOTE X-CLIENT-REFERRAL -fi - - -cat < - - - - - - - - -END_HTML diff --git a/templates/edit_course.sh b/templates/edit_course.sh deleted file mode 100755 index 0e1cd73..0000000 --- a/templates/edit_course.sh +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -SUP_FIELDS=(SUMMARY COMMENT) -[ -z "$values[UID]" ] && values[UID]="$(uuidgenerator)" - -dtstart="$values[DTSTART]" -[ -z "$dtstart" ] && dtstart=$(date +%Y%m%dT%H%M%S) - -echo "$dtstart" |case "$dtstart" in - *Z) - sed -rn 's:^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$:\1-\2-\3 \4\:\5\:\6 UTC:p' - ;; - TZID*) - sed -rn 's:^TZID=(.+)\:([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$:TZ="\1" \2-\3-\4 \5\:\6\:\7:p' - ;; - *) - sed -rn 's:^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$:\1-\2-\3 \4\:\5\:\6:p' - ;; -esac |read dts_date - -dts_year=$(date -d "$dts_date" +%Y) -dts_month=$(date -d "$dts_date" +%m) -dts_dom=$(date -d "$dts_date" +%d) -dts_dow=$(date -d "$dts_date" +%u) -dts_weekday=$(date -d "$dts_date" +%A) -dts_hour=$(date -d "$dts_date" +%H) -dts_min=$(date -d "$dts_date" +%M) -dts_sec=$(date -d "$dts_date" +%S) - -rrule="$values[RRULE]" -rr_int="$(echo $rrule |sed -rn 's:^.*INTERVAL=([0-9]+)(;.*)?$:\1:p')" -rr_count="$(echo $rrule |sed -rn 's:^.*COUNT=([0-9]+)(;.*)?$:\1:p')" -rr_freq="$(echo $rrule |sed -rn 's:^.*FREQ=(YEARLY|MONTHLY|WEEKLY|DAILY)(;.*)?$:\1:p')" -rr_uyear="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T[0-9]{6}Z(;.*)?$:\1:p')" -rr_umonth="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T[0-9]{6}Z(;.*)?$:\2:p')" -rr_uday="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T[0-9]{6}Z(;.*)?$:\3:p')" -if [ -n "$rr_count" ]; then - rr_limit=COUNT -elif [ -n "$rr_uyear" ]; then - rr_limit=UNTIL -else - rr_limit=ETERN -fi - -echo '
      ' -echo '
      ' -echo '' -echo '
      ' -echo '

      '$(l10n SUMMARY)'

      ' -echo ' ' -echo '

      '$(l10n COMMENT)'

      ' -for n in COMMENT COMMENT{0..10}; do [ -n "$values[$n]" ] &&\ - echo ' ' -done - -echo '
      ' -echo '

      '$(l10n DTSTART)'

      ' -echo ' ' -echo ' ' -echo '
      ' -for wd in mon tue wed thu fri sat sun; do - echo -n ''$(date -d $wd +%a)'' -done -fdom=$(date -d ${dts_year}-${dts_month}-1 +%u) -while [ "$fdom" -gt 1 ]; do - echo -n '' - fdom=$(($fdom - 1)) -done -cnt=$(date -d ${dts_year}-${dts_month}-1 +%s) -while [ "$(date -d @$cnt +%m)" = "$dts_month" ]; do - dn=$(date -d @$cnt +%d) - echo -n '' - cnt=$(($cnt + 86400)) -done -echo '
      ' -echo ' '$(l10n time)':' -echo ' :' - -echo '
      ' -echo '

      '$(l10n RRULE)'

      ' -echo ' ' -echo ' '$(l10n t_every)'' -echo ' ' -echo ' ' -echo ' ' -echo ' '$(l10n t_eternal)'
      ' -echo '
      ' -echo ' ' -echo ' '$(l10n t_times)'' -echo ' ' -echo ' '$(l10n t_until)'' -echo ' ' -echo ' ' -echo ' ' - -echo '
      ' -echo '

      '$(l10n course_attendance)'

      ' -listcards |while read card; do - cfile="$_DATA/vcard/$card" - aname=$(sed -rn 's:^N(;[^"\:]+|;"[^"]+")*\:([^;]*)(\;[^;]*)(\;[^;]*)?(\;[^;]*)?(\;[^;]*)?$:\5 \3 \4 \2 \6:p' "$cfile" \ - |sed -r 's:,: :;s:\;: :g;s: +: :g' \ - |tr -d '\r' - ) - selected=$(sed -rn "/^$id\t$card\$/{s;^.*\$;checked=\"checked\";p;q}" "${_DATA}/mappings/attendance") - echo ' ' -done - -echo '
      ' -echo ' ' -echo '' -echo '
      ' - diff --git a/templates/edit_prescription.sh b/templates/edit_prescription.sh deleted file mode 100755 index b7a9af4..0000000 --- a/templates/edit_prescription.sh +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -check(){ [ "$1" = "$2" ] && printf checked} -[ -z $mpx[presctype] ] && mpx[presctype]=doctor_compulsory - -cat < - - - - - - - - - -
      - - - - - - - - -
      - - - - -
      - - - - - - -
      - -
      - - -
      - - -
      - - - -
      - - -
      - -
      -

      $(l10n therapy_prescription)

      - - - - - - - -
      - - - - -
      -

      $(l10n prescription_by_catalogue)

      - - -
      - - -
      - - -
      - - -
      - - - -
      - - -
      - - -
      - - -
      - -
      - - - -

      - - - -

      - -$( for n in {0..10}; do - if [ "$n" -eq 0 -o -n "${mpx[quantity$n]}" -o -n "${mpx[remidy$n]}" -o -n "${mpx[quantity_weekly$n]}" ]; then - printf '' - else - printf '' - fi - printf '

      - - - -

      - ' "$(l10n quantity)" "${mpx[quantity$n]}" \ - "$(l10n remidy)" "${mpx[remidy$n]}" \ - "$(l10n quantity_weekly)" "${mpx[quantity_weekly$n]}" -done ) - -
      -

      - - -
      - - -

      -
      -

      - - -

      -
      -

      - - - - -

      -
      - -
      - - - -
      - - -END_HTML diff --git a/templates/email.html.sh b/templates/email.html.sh deleted file mode 100755 index 7f93893..0000000 --- a/templates/email.html.sh +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -cat < - $(l10n filter_label) - -
      - -
      -
      -EOF - -# vi:set filetype=html: diff --git a/templates/frame.html.sh b/templates/frame.html.sh deleted file mode 100755 index b8128a3..0000000 --- a/templates/frame.html.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/zsh - -# Copyright 2014, 2015, 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -. "${_EXEC}/templates/text_frame.sh" -[ -x "${_EXEC}/templates/text_${PAGE}.sh" ] && . "${_EXEC}/templates/text_${PAGE}.sh" - -bmfile="${_DATA}/mappings/bookmarks" - -cat < - - - - $(l10n p_${PAGE}) - - - - - - -EOF - -debug BODY; [ -x "${BODY}" ] && . "${BODY}" || printf %s 'Error' - -cat < - $(l10n configure) - -' -EOF - -# vi:set filetype=html: diff --git a/templates/text_cards.sh b/templates/text_cards.sh deleted file mode 100755 index 3168a69..0000000 --- a/templates/text_cards.sh +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright 2014, 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -item_name[PHOTO]="Foto" -item_name[LOGO]="Logo" -item_name[FN]="Voller Name" -item_name[N]="Name" -item_name[n_pre]="Titel" -item_name[n_first]="Vorname" -item_name[n_middle]="Mittelnamen" -item_name[n_last]="Nachname" -item_name[n_post]="Zusätze" -item_name[NICKNAME]="Spitzname" -item_name[SOUND]="Aussprache" -item_name[GENDER]="Geschlecht" -item_name[KIND]="Typ" -item_name[TITLE]="Beruf" -item_name[ROLE]="Position" -item_name[ORG]="Organisation" -item_name[MEMBER]="Mitglied" -item_name[CATEGORIES]="Kategorien" -item_name[ANNIVERSARY]="Jubiläum" -item_name[BDAY]="Geburtstag" -item_name[EMAIL]="E-Mail" -item_name[TEL]="Telefon" -item_name[phone_typeselect]="Typ" -item_name[phone_home]="Privat" -item_name[phone_cell]="Mobil" -item_name[phone_work]="Büro" -item_name[phone_fax]="Fax" -item_name[tHOME]="Privat:" -item_name[tWORK]="Büro:" -item_name[tCELL]="Mobil:" -item_name[tFAX]="Fax:" -item_name[tVOICE]="" -item_name[IMPP]="Chat" -item_name[ADR]="Anschrift" -item_name[URL]="Webseite" -item_name[LANG]="Sprache" -item_name[NOTE]="Notiz" - -item_name[RELATED]="Kontakte" - -item_name[BEGIN]="" -item_name[CALADRURI]="" -item_name[CALURI]="" -item_name[CLASS]="" -item_name[CLIENTPIDMAP]="" -item_name[END]="" -item_name[FBURL]="" -item_name[GEO]="" -item_name[MAILER]="" -item_name[NAME]="" -item_name[PRODID]="" -item_name[PROFILE]="" -item_name[REV]="" -item_name[SORT-STRING]="" -item_name[SOURCE]="" -item_name[TZ]="" -item_name[UID]="" -item_name[VERSION]="" -item_name[XML]="" - -item_name[X-HEALTH-INSURANCE]="Kran­ken­ver­sich­er­ung" -item_name[hi_from_list]="Aus Liste" -item_name[hi_other]="Andere" -item_name[hi_company]="Ver­sich­er­ungs­ge­sell­schaft" -item_name[hi_number]="Ver­sich­er­ten­num­mer" -item_name[hi_status]="Ver­sich­er­ten­sta­tus" -item_name[X-HEALTH-INSURANCE-NOCONTRIB]="Zu­zahl­ungs­be­frei­ung" -item_name[X-CLIENT-REFERRAL]="Empfehl­ung durch" -item_name[prescriptions]="Verordnungen" -item_name[new_prescription]="Neue Verordnung" -item_name[no_icd]="Kein ICD Code" - -item_name[X-ZACK-JOINDATE]="An­mel­de­da­tum" -item_name[X-ZACK-LEAVEDATE]="Ab­mel­de­da­tum" -item_name[label_join]="Anm." -item_name[label_leave]="Abm." - -item_name[mail_all]="Mail an alle gelisteten" -item_name[edit]="Bearbeiten" -item_name[edit_categories]="Kategorien Bearbeiten" -item_name[vcf_export]="Vcard Exportieren" -item_name[control]="Aktionen" -item_name[edit_update]="Daten übernehmen" -item_name[edit_cancel]="Abbrechen" -item_name[edit_delete]="Eintrag löschen" -item_name[edit_addfieldtext]="Neues Feld" -item_name[edit_addfield]="+" -item_name[edit_deletefield]="X" -item_name[filter_label]="Filter" -item_name[filter_placeholder]="Begriffe zur Eingrenzung eingeben" -item_name[filter_type]="Filtertyp" -item_name[filter_order]="Sortierung" -item_name[filter_all]="Alles" -item_name[filter_name]="Name" -item_name[filter_firstname]="Vorname" -item_name[filter_lastname]="Nachname" -item_name[filter_street]="Straße" -item_name[filter_zip]="PLZ." -item_name[filter_phone]="Telefon" -item_name[filter_birthyear]="Geburtsjahr" -item_name[filter_bdate]="Geburtsdatum" -item_name[filter_course]="Kurs" -item_name[filter_apply]="Filtern" -item_name[filter_cancel]="Filter löschen" -item_name[newcard]="Neuen Eintrag anlegen" -item_name[course_attendance]="Kursteilnahme" - -item_name[gender_none]="keine Angabe" -item_name[gender_female]="Weiblich" -item_name[gender_male]="Männlich" -item_name[gender_other]="Sonstiges" - -item_name[female]="♀" -item_name[male]="♂" -item_name[other]="⚥" -item_name[none]="⚪" - diff --git a/templates/text_categories.sh b/templates/text_categories.sh deleted file mode 100755 index 00c61ce..0000000 --- a/templates/text_categories.sh +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2014, 2015 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -item_name[cat_remove]="-" -item_name[cat_add]="+" -item_name[cat_newlabel]="neue Kategorie" -item_name[cat_update]="Zuweisungen übernehmen" -item_name[categories_label]="Kategorien" diff --git a/templates/text_courses.sh b/templates/text_courses.sh deleted file mode 100755 index b86240a..0000000 --- a/templates/text_courses.sh +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2014, 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -declare -A item_name - -item_name[SUMMARY]="Bezeichnung" -item_name[COMMENT]="Kommentar" -item_name[DTSTART]="Beginn" -item_name[DURATION]="Dauer" -item_name[RRULE]="Regelmäßigkeit" - -item_name[DAILY]="Tage" -item_name[WEEKLY]="Wochen" -item_name[MONTHLY]="Monate" -item_name[YEARLY]="Jahre" - -item_name[sDAILY]="Täglich" -item_name[sWEEKLY]="Wöchentlich" -item_name[sMONTHLY]="Monatlich" -item_name[sYEARLY]="Jährlich" - -item_name[newcourse]="Neuen Eintrag anlegen" -item_name[time]="Uhrzeit" -item_name[edit_update]="Daten übernehmen" -item_name[edit_cancel]="Abbrechen" -item_name[edit_delete]="Eintrag löschen" -item_name[edit_addfieldtext]="Neues Feld" -item_name[edit_addfield]="+" -item_name[edit_dtscal]="✓" -item_name[edit]="Bearbeiten" -item_name[ics_export]="ICal exportieren" -item_name[courselist]="Kursliste (PDF)" - -item_name[course_attendance]="Teilnahme" -item_name[course_mail]="Mail an Teilnehmende" - -item_name[sort_order]="Sortierung" -item_name[order_DOW]="Wochentag" -item_name[order_TOD]="Uhrzeit" -item_name[order_apply]="Sortieren" - -item_name[t_every]="Alle" -item_name[t_eternal]="ewig" -item_name[t_times]="mal" -item_name[t_until]="Bis" -item_name[t_oclock]="Uhr" diff --git a/templates/text_frame.sh b/templates/text_frame.sh deleted file mode 100755 index 75e1511..0000000 --- a/templates/text_frame.sh +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2014 - 2016, 2018 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -item_name[p_cards]="Personen" -item_name[p_courses]="Kurse" -item_name[p_email]="Email" -item_name[p_error]="Fehler" -item_name[p_categories]="Kategorien" -item_name[p_prescriptions]="Verschreibungen" -item_name[p_therapy]="Therapie" -item_name[nc_edit]="⚙" -#item_name[nc_edit]="☰" -item_name[bm_add]="+" -item_name[bm_del]="-" -item_name[configure]="Anpassen" diff --git a/templates/text_prescriptions.sh b/templates/text_prescriptions.sh deleted file mode 100755 index 8934bc7..0000000 --- a/templates/text_prescriptions.sh +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -. $_EXEC/templates/text_cards.sh - -item_name[therapy_prescription]="Heil­mit­tel­ver­ord­nung" -item_name[prescriptions_current]="Aktuelle Verordnungen" -item_name[prescriptions_past]="Frühere Verordnungen" -item_name[newprescription]="Neue Verordnung" -item_name[date]="Datum" -item_name[name]="Name d. Versicherten" -item_name[bday]="geb. am" -item_name[addcontrib]="Zuzahlung" -item_name[contribconfirm]="Zuzahlung erfolgt am..." -item_name[contribreceipt]="Quit­tung heraus­ge­ge­ben" -item_name[prescreviewed]="Verordnung geprüft" -item_name[prescreview]="Verordnung prüfen!" -item_name[quantity]="Ver­ord­nungs­men­ge" -item_name[remidy]="Heil­mit­tel nach Maß­ga­be des Ka­ta­lo­ges" -item_name[prescfirst]="Erst­ver­ord­nung" -item_name[prescfollow1]="1. Folge-­VO" -item_name[prescfollow2]="2. Folge-­VO" -item_name[prescother]="VO außer­halb des Re­gel­falls" -item_name[presccontinual]="Lang­frist­ver­ord­nung" -item_name[grouptherapy]="Grup­pen­the­ra­pie" -item_name[housecall]="Haus­be­such" -item_name[report]="The­ra­pie­be­richt" -item_name[indicator]="In­di­ka­tions­schlüssel" -item_name[icd10]="ICD-10-Code" -item_name[indicator_reading]="Befund Beschreibung" -item_name[insurance]="Krankenkasse bzw. Kostenträger" -item_name[prescription_by_catalogue]="Verordnung nach Maßgabe des Kataloges (Regelfall)" -item_name[therapy_start]="Be­hand­lungs­be­ginn spä­test. am" -item_name[quantity_weekly]="An­zahl pro Wo­che" -item_name[save]="Speichern" -item_name[cancel]="Abbrechen" -item_name[delete]="Löschen" -item_name[therapy]="Zur Therapie" - -item_name[doctor]="Arzt" -item_name[dentist]="Zahn­arzt" -item_name[altpractition]="Heil­prak­tiker" -item_name[noprescription]="Ohne Ver­ord­nung" -item_name[selfpaid]="Selbst­zah­lend" -item_name[private]="Pri­vat" -item_name[compulsory]="Ge­setz­lich" - -item_name[therapy_dates]="Be­hand­lungs­ter­mi­ne" - -item_name[issuer]="Ausgestellt durch" -item_name[issuer_from_list]="Aus Liste" -item_name[issuer_other]="Andere" diff --git a/templates/text_therapy.sh b/templates/text_therapy.sh deleted file mode 100755 index a2735b2..0000000 --- a/templates/text_therapy.sh +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -. $_EXEC/templates/text_prescriptions.sh - -item_name[prescriptionlist]="Zur Verordnungsliste" -item_name[delete_session]="Therapiesitzung entfernen" -item_name[therapist]="Therapeut" -item_name[number]="Nr." -item_name[signature]="Un­ter­schrift" -item_name[weekly]="p. Woche" -item_name[notes]="Notizen" -item_name[trailsave]="Speichern für weitere Felder" diff --git a/templates/therapy.html.sh b/templates/therapy.html.sh deleted file mode 100755 index 1821c24..0000000 --- a/templates/therapy.html.sh +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright 2016, 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -t_session_note(){ - session_n="$1" - note_n="$2" - - color=session${session_n}_note${note_n}_color - unset c0 c1 c2 c3 c4 c5 c6 c7 - - if [ "$note_n" -eq 1 -o -n "${tpy[session${session_n}_note${note_n}]}" ]; then - printf '' - else - printf '' - fi - - _checked="$(validate "$tpy[$color]" '#(888|00A|0A0|0AA|A00|A0A|AA0)' '#FFF')" - cat <<-EOF -
      - - - - - - - - - -
      - EOF -} - -t_session(){ - session_n="$1" - - sid=session${session_n} - - if [ "$tpy[${sid}_sigset]" = pos ]; then - sigcheck=checked - else - unset sigcheck - fi - - cat <<-EOF - - - -
      - - $(n=1; - while [ -n "${tpy[session${session_n}_note${n}]+x}" ]; do - if [ -n "${tpy[session${session_n}_note${n}]}" ]; then - x=$n - fi - n=$(($n + 1)) - done - for n in $(seq 1 $((${x:-0} + 3)) ); do t_session_note $session_n $n; done - ) - - -
      - EOF -} - -therapy_sessions(){ - n=1; while [ -n "${tpy[session${n}]}" ]; do - t_session $n - n=$(($n+1)) - done - - sid=session$n - - cat <<-EOF -
      - - -
      - EOF -} - -cat <$(l10n therapy) - -
      -

      $client_name

      - < $(l10n prescriptionlist) -
      - -
      -

      $(l10n therapy_prescription)

      - ${mpx[insurance]} - ${mpx[date]} - - - - ${mpx[prescno]:+$(l10n presc${mpx[prescno]})} - ${mpx[grouptherapy]:+$(l10n grouptherapy)} - ${mpx[housecall]:+$(l10n housecall)} - ${mpx[report]:+$(l10n report)} - -
        $(for n in '' {0..10}; do - [ -n "${mpx[remidy$n]}" ] && \ - printf '
      • %s %s %s
      • ' "${mpx[quantity$n]}" \ - "${mpx[remidy$n]}" \ - "${mpx[quantity_weekly$n]:+($mpx[quantity_weekly$n] $(l10n weekly))}" - done)
      - - ${mpx[indicator]:+${mpx[indicator]}} - ${mpx[icd10]:+ ${mpx[icd10]}} - - ${mpx[addcontrib]:+ - - } - - - -
      ${mpx[indicator_reading]}
      -
      - -
      - - - -
      - -

      $(l10n notes)

      - - -
      - - -
      - -

      $(l10n timesheet)

      - - - - $(for n in '' {0..10}; do - printf ' - - - \n' \ - "$mpx[tsgoal${n}]" "$mpx[tsactual${n}]" "$((${mpx[tsgoal${n}]:-0} - ${mpx[tsactual${n}]:-0}))" - done) -
      $(l10n time_goal)$(l10n time_actual)$(l10n time_difference)
      %s
      - -
      - - - - -EOF - -therapy_sessions - -_checked="$(validate "$tpy[penwidth]" '(4|12|36)' '4')" -cat < - - - - -EOF - -_checked="$(validate "$tpy[color]" '#(0[0A]{2}|A00|A0A|AA0|FFF)' '#000')" -cat < - - - - - - - - - - WARNING: Background Image not available! - - - - - - - - - - -EOF - -# vi:set filetype=html: diff --git a/templates/view_card.sh b/templates/view_card.sh deleted file mode 100755 index 13dbab7..0000000 --- a/templates/view_card.sh +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright 2014 - 2017 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -list_items(){ - item="$1" - [ -n "${values[$item]+x}" ] && \ - printf '

      %s

      \n' "$(l10n $item)" - for n in "$item" "$item"{0..100}; do - if [ -z "${values[$n]+x}" ]; then - break - else case "$item" in - EMAIL) - printf '\n' \ - "$(attribsafe "${values[$n]}")" "$(htmlsafe "${values[$n]}")" - ;; - *) - printf '%s\n' \ - "$item" "$(htmlsafe ${values[$n]})" - ;; - esac; fi - done -} - -list_section(){ - printf '
      ' "$1" - shift 1 - for each in $@; do - list_items "$each" - done - printf '
      ' -} - -n=$(printf %s "$values[N+3] $values[N+1] $values[N+2] $values[N+0] $values[N+4]" \ - | sed -r ':X;$!{N;bX}; s;^[\n ]+;;; s;[\n ]+$;;; s;[\r\t\n ]+; ;g;' - ) -fullname="${n:-${values[FN]:-${values[NICKNAME]}}}" - -hi_company="${values[X-HEALTH-INSURANCE+0]}" - hi_number="${values[X-HEALTH-INSURANCE+1]}" - hi_status="${values[X-HEALTH-INSURANCE+2]}" - -printf '
      -

      %s

      -' "$fullname" -[ -n "$values[GENDER]" ] && printf ' - %s - ' "$(l10n "$values[GENDER]")" - -for n in NICKNAME NICKNAME{0..100}; do - [ -z "${values[$n]+x}" ] && break \ - || printf ' - aka. %s - ' "$(htmlsafe ${values[$n]})" -done - -[ -n "$values[BDAY]" ] && printf ' - *: %s - ' "$(htmlsafe "$values[BDAY]")" -[ -n "$values[X-ZACK-JOINDATE]" ] && printf ' - %s: %s - ' "$(l10n label_join)" "$(htmlsafe "$values[X-ZACK-JOINDATE]")" -[ -n "$values[X-ZACK-LEAVEDATE]" ] && printf ' - %s:%s - ' "$(l10n label_leave)" "$(htmlsafe "$values[X-ZACK-LEAVEDATE]")" - -[ -n "$values[SOUND]" ] && printf ' - ' "$values[SOUND]" - -[ -n "$values[PHOTO]" ] && printf ' - - ' "${values[PHOTO_TYPE]}" "${values[PHOTO]}" - -[ -n "$values[LOGO]" ] && printf ' - - ' "${values[LOGO_TYPE]}" "${values[LOGO]}" - -if [ "$PROFILE" = circus ]; then - printf '
      ' - - list_section phone TEL - list_section message EMAIL IMPP URL - list_section address ADR - list_section note NOTE - - printf '

      %s

        ' "$(l10n course_attendance)" - sed -rn 's:(.*)\t'"$id"'$:\1:p' "$_DATA/mappings/attendance" |while read each; do - cname="$(sed -rn 's:^SUMMARY\:(.*)$:\1:p' "$_DATA/ical/$each")" - printf '
      • %s
      • ' "$each" "$(htmlsafe $cname)" - done - printf '
      ' - list_items CATEGORIES - printf '
      ' - -elif [ "$PROFILE" = medical ]; then - list_items ADR - list_items URL - printf '' - - list_section phone TEL EMAIL IMPP - - printf '

      %s

      ' "$(l10n X-HEALTH-INSURANCE)" - [ -n "$hi_company" ] && printf '%s' \ - "$(htmlsafe "$hi_company")" - [ -n "$hi_number" ] && printf ' %s' \ - "$(l10n hi_number)" "$(htmlsafe "$hi_number")" - [ -n "$hi_status" ] && printf ' %s' \ - "$(l10n hi_status)" "$(htmlsafe "$hi_status")" - printf '
      ' - - list_section note NOTE X-CLIENT-REFERRAL - - printf '

      %s

        ' "$(l10n prescriptions)" - declare -A mpx - find "$_DATA/prescriptions/" -name "${id%.vcf}.*.mpx" \ - | while read pfile; do - mpx=(); cat "$pfile" |while read -r line; do - val="${line#*:}" - mpx[${line%%:*}]="$(htmlsafe "${val//\\n/$BR}")" - done - printf '
      • %s: %s - %s
      • ' \ - "${id}" "${pfile##*/}" "${mpx[date]}" "${mpx[indicator]}" \ - "$([ -n "${mpx[remidy]}" ] && printf '%s %s' "${mpx[quantity]}" "${mpx[remidy]}" - for n in {0..10}; do - [ -n "${mpx[remidy${n}]}" ] && printf ', %s %s' "${mpx[quantity${n}]}" "${mpx[remidy${n}]}" - done - )" - done |sort -r - printf '
      ' -fi diff --git a/templates/view_course.sh b/templates/view_course.sh deleted file mode 100755 index 23088f1..0000000 --- a/templates/view_course.sh +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2014 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -dtstart="$values[DTSTART]" -[ -z "$dtstart" ] && dtstart=$(date +%Y%m%dT%H%M%S) - -echo "$dtstart" |case "$dtstart" in - *Z) - sed -rn 's:^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$:\1-\2-\3 \4\:\5\:\6 UTC:p' - ;; - TZID*) - sed -rn 's:^TZID=(.+)\:([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$:TZ="\1" \2-\3-\4 \5\:\6\:\7:p' - ;; - *) - sed -rn 's:^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})$:\1-\2-\3 \4\:\5\:\6:p' - ;; -esac |read dts_date - -dts_year=$(date -d "$dts_date" +%Y) -dts_month=$(date -d "$dts_date" +%m) -dts_dom=$(date -d "$dts_date" +%d) -dts_dow=$(date -d "$dts_date" +%u) -dts_weekday=$(date -d "$dts_date" +%A) -dts_hour=$(date -d "$dts_date" +%H) -dts_min=$(date -d "$dts_date" +%M) -dts_sec=$(date -d "$dts_date" +%S) - -rrule="$values[RRULE]" -rr_int="$(echo $rrule |sed -rn 's:^.*INTERVAL=([0-9]+)(;.*)?$:\1:p')" -rr_count="$(echo $rrule |sed -rn 's:^.*COUNT=([0-9]+)(;.*)?$:\1:p')" -rr_freq="$(echo $rrule |sed -rn 's:^.*FREQ=(YEARLY|MONTHLY|WEEKLY|DAILY)(;.*)?$:\1:p')" -rr_uyear="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T[0-9]{6}Z(;.*)?$:\1:p')" -rr_umonth="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T[0-9]{6}Z(;.*)?$:\2:p')" -rr_uday="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T[0-9]{6}Z(;.*)?$:\3:p')" -rr_udate="$(echo $rrule |sed -rn 's:^.*UNTIL=([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z(;.*)?$:\1-\2-\3 \4\:\5\:\6:p')" -if [ -n "$rr_count" ]; then - rr_limit=COUNT -elif [ -n "$rr_uyear" ]; then - rr_limit=UNTIL -else - rr_limit=ETERN -fi - -coursemail="" - -echo '
      ' -echo '
      ' -echo '' -echo '
      ' -echo '

      '$values[SUMMARY]'

      ' -echo ' '$(date -d "$dts_date" "+%A %B %d, %Y - %H:%M") $(l10n t_oclock)'' -[ "$rr_int" -eq 1 ] \ -&& echo ''$(l10n s$rr_freq)'' \ -|| echo ''$(l10n t_every) ${rr_int} $(l10n $rr_freq)'' -case "$rr_limit" in - COUNT) - m1=$(echo $rr_freq |sed -r "s:DAILY:day:g;s:WEEKLY:week:g;s:MONTHLY:month:g;s:YEARLY:year:g;") - m2=$(($rr_int * $rr_count)) - echo ''$(l10n t_until) $(date -d "$dts_date + $m2 $m1" "+%A %B %d, %Y - %H:%M")'' - ;; - UNTIL) - echo ''$(l10n t_until) $(date -d "$rr_udate" "+%A %B %d, %Y - %H:%M")'' - ;; -esac -echo '
      ' - -echo '
      ' -echo '

      '$(l10n COMMENT)'

      ' -for n in COMMENT COMMENT{0..10}; do [ -n "$values[$n]" ] &&\ - echo '

      '$(echo "$values[$n]" |sed -r "s:$:
      :g" )'

      ' -done -echo '
      ' - -echo '
      ' -echo '

      '$(l10n course_attendance)'

      ' -list_attendance "$id" |sort -k 2 |sed -r 's:^([^ ]+) (.*)$:\2:' -echo '
      ' - -echo '
      ' -echo ' '$(l10n edit)'' -echo ' '$(l10n courselist)'' -echo ' '$(l10n ics_export)'' -echo ' '$(l10n course_mail)'' -echo '
      ' -echo '' -echo '
      ' -echo '
      ' - diff --git a/templates/view_prescription.sh b/templates/view_prescription.sh deleted file mode 100755 index ced7241..0000000 --- a/templates/view_prescription.sh +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2016 Paul Hänsch -# -# This file is part of Confetti. -# -# Confetti is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Confetti is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with Confetti. If not, see . - -check(){ [ "$1" = "$2" ] && printf checked} - -cat < -
      - -${mpx[insurance]} -
      - - -${mpx[name]} -${mpx[bday]} - -
      - -${mpx[date]} -
      - -
      -

      $(l10n therapy_prescription)

      -
      - -${mpx[addcontrib]} - -${mpx[contribconfirm]} - -
      - - - -
      -

      $(l10n prescription_by_catalogue)

      - -
      - -
      - -
      - -
      - - -
      - -
      - -
      - -
      - -
      - - - - ${mpx[quantity]} - ${mpx[remidy]} - ${mpx[quantity_weekly]} -$( for n in {0..10}; do - [ -n "${mpx[quantity$n]}" -o -n "${mpx[remidy$n]}" -o -n "${mpx[quantity_weekly$n]}" ] \ - && printf ' - %s - %s - %s - ' "${mpx[quantity$n]}" "${mpx[remidy$n]}" "${mpx[quantity_weekly$n]}" -done ) -
      -

      - - ${mpx[indicator]} -
      - - ${mpx[icd10]} -

      -
      -

      - - ${mpx[indicator_reading]} -

      -
      - -

      - - $(therapy_dates "$id" \ - | while read date; do - printf '%s' "$date" - done - ) -

      - -

      - - $mpx[issuer] -

      - -
      -$(l10n edit) -$(l10n therapy) -
      - - -END_HTML