From: Paul Hänsch Date: Sat, 15 May 2021 11:13:32 +0000 (+0200) Subject: Merge commit 'a27498203e5d589514f082a0fcdcca53e31c595e' into cgilite X-Git-Url: https://git.plutz.net/?a=commitdiff_plain;h=refs%2Fheads%2Fcgilite;hp=a27498203e5d589514f082a0fcdcca53e31c595e;p=lobster Merge commit 'a27498203e5d589514f082a0fcdcca53e31c595e' into cgilite --- diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. 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/cards/edit_card.sh b/cards/edit_card.sh new file mode 100755 index 0000000..16d7599 --- /dev/null +++ b/cards/edit_card.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +# Copyright 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 . + +locktimeout=900 +. "$_EXEC"/session_lock.sh + +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 + SET_COOKIE session message="EDITLOCK" + REDIRECT "/cards/?o=${order}&f=${filter}#${card#/}" +fi diff --git a/cards/export_card.sh b/cards/export_card.sh new file mode 100755 index 0000000..0918032 --- /dev/null +++ b/cards/export_card.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# Copyright 2014, 2015, 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 . + +card="$(GET card |PATH)" +cardfile="$_DATA/vcard/${card##*/}" + +. $_EXEC/pdiread.sh +. $_EXEC/cgilite/file.sh + +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..d6185c8 --- /dev/null +++ b/cards/export_csv.sh @@ -0,0 +1,70 @@ +#!/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)"; } |pdi_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" |pdi_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" |pdi_unescape)" + [ "$gen" ] && l10n "gender_$gen" + ;; + *) pdi_value "$card" "$item" "$n" |pdi_unescape + ;; + esac; done \ + | sed -E 's;";\\";g;' +} + +printf '%s\r\n' \ + 'Content-Type: text/csv; charset=utf-8' \ + 'Content-Disposition: inline; filename="lobster_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 hi_company)" "$(l10n hi_number)" \ +| sed -E 's;­\;;;g;' + + +filter_cards \ +| order_cards \ +| while read cardfile; do + card="$(pdi_load "$cardfile")" + IFS=';' read -r h_comp h_num h_stat <<-EOF + $(pdi_value "$card" X-HEALTH-INSURANCE |sed -E 's;";\\";g;') + EOF + + 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)" "$(pdi_unescape "$h_comp")" "$(pdi_unescape "$h_num")" +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..b48e54e --- /dev/null +++ b/cards/index.cgi @@ -0,0 +1,30 @@ +#!/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=lastname +edit="${edit##*/}" + +list_hi_companies(){ + sed -rn 's;^X-HEALTH-INSURANCE:([^\;]+)\;.*$;\1;p' ${_DATA}/vcard/*vcf \ + | sort -u +} + +{ w_filter_diag + printf ' + [form class="newcard" action="/cards/new_card.sh" method="POST" + [button type="submit" %s] + [a href="#top" . %s] + ]' "$(l10n newcard)" "$(l10n page_top)" + [ "$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..f991e4f --- /dev/null +++ b/cards/l10n.sh @@ -0,0 +1,65 @@ +# 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";; + therapy) printf %s "Therapie";; + therapies) printf %s "Therapien";; + + 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..616d6ab --- /dev/null +++ b/cards/list.sh @@ -0,0 +1,161 @@ +#!/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 + [span .card-anchor #${cardfile##*/}] + [form .card 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 + card_item "$card" SOUND PHOTO LOGO + )] + [div .section .phone $( + edit_item "$card" TEL 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 .insurance $(edit_item "$card" X-HEALTH-INSURANCE)] + [div .section .note $(edit_item "$card" NOTE X-CLIENT-REFERRAL)] + [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")" + local N1 N2 N3 N4 N5 + IFS=\; read N1 N2 N3 N4 N5 <<-EOF + $(pdi_value "$card" N |pdi_unescape |HTML) + EOF + + cat <<-EOF + [div .card #${cardfile##*/} + [div .section .basic + [h2 .item .FN . $N4 $N1 $N5] + [span .item .firstname . $N2 $N3] + $( + card_item "$card" GENDER NICKNAME BDAY X-ZACK-JOINDATE X-ZACK-LEAVEDATE SOUND PHOTO LOGO + )] + [div .section .address $(card_item "$card" ADR)] + [div .section .phone $(card_item "$card" TEL EMAL IMPP URL)] + [div .section .insurance $(card_item "$card" X-HEALTH-INSURANCE)] + [div .section .note $(card_item "$card" NOTE X-CLIENT-REFERRAL)] + [div .section .therapies $(card_item "$card" therapies)] + [div .control + [a .item .button href="/cards/edit_card.sh?card=${cardfile##*/}" $(l10n edit)] + [a .item .button 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" ]; then + cat "$cachefile" + else + print_card "$cardfile" |tee "$cachefile" + fi + done +} + +filter_cards(){ + local filter f fex='x;p;' + + filter="$(printf %s "${filter}" \ + | sed -E 's;[]\/\(\)\\\$\?\.\+\*\;\[\{\}];\\&;g; + '"$upcase" + )^" + + while [ "$filter" ]; do + f="${filter%%^*}" filter="${filter#*^}" + debug "Filter: $f" + case $f in + '') break + ;; + 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 + printf '%s\n' "$cardfile" "$(grep -vxE " *${CR}?" "$cardfile")" + done \ + | sed -En ':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..a8afc7a --- /dev/null +++ b/cards/new_card.sh @@ -0,0 +1,66 @@ +#!/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" + +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:$(pdi_escape "$ln" "$fn" "$mn" "" "") + FN:$(pdi_escape "${fn}${mn:+ }${mn} ${ln}") + BDAY:$(parse_date "${byear}-${bmonth}-01") + TEL:$(pdi_escape "$tel") + TEL;TYPE=CELL:$(pdi_escape "$tcell") + EMAIL:$(pdi_escape "$email") + X-ZACK-JOINDATE:$(parse_date "$date") + ADR: + NOTE:$(pdi_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..7bfca2f --- /dev/null +++ b/cards/update_card.sh @@ -0,0 +1,131 @@ +#!/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 tempfile +unset vcf field cnt delete_key + +filter="$(REF f)" +order="$(REF o)" + +card="$(POST card |PATH)"; card="${card##*/}" +cardfile="$_DATA/vcard/${card}" + +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 + +# [ "${_POST[hi_select]}" = "list" ] || _POST[hi_company]="${_POST[hi_other]}" +# [ -n "${_POST[hi_company]}${_POST[hi_number]}${_POST[hi_status]}" ] \ +# && _POST[X-HEALTH-INSURANCE]="$(pdi_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 "$(pdi_escape "$n1" "${n2%% *}" "${n3# }" "$n4" "$n5")")" +vcf="$(pdi_update_value "$vcf" FN 1 "$(pdi_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}]}" "$(pdi_escape "$(POST "$field" "$cnt")")" + # ;; + X-HEALTH-INSURANCE) + hi_select="$(POST "$field" "$cnt")" + if [ "$hi_select" = list ]; then + vcf="$(pdi_update_value "$vcf" "$field" "$cnt" "$(pdi_escape "$(POST "hi_company" "$cnt")" \ + "$(POST "hi_number" "$cnt")" \ + "$(POST "hi_status" "$cnt")" \ + )")" + elif [ "$hi_select" = other ]; then + vcf="$(pdi_update_value "$vcf" "$field" "$cnt" "$(pdi_escape "$(POST "hi_other" "$cnt")" \ + "$(POST "hi_number" "$cnt")" \ + "$(POST "hi_status" "$cnt")" \ + )")" + fi + ;; + 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" "$(pdi_escape "$(POST "$field" "$cnt")")")" + ;; + *) + vcf="$(pdi_update_value "$vcf" "$field" "$cnt" "$(pdi_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" | sed -E '/^$/d; s/^([^:]+);:/\1:/;' >"$tempfile" + +case "$action" in + addfield) + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card#/}" + ;; + update) + 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" + REDIRECT "/cards/?o=${order}&f=${filter}" + ;; +esac diff --git a/cards/widgets.sh b/cards/widgets.sh new file mode 100755 index 0000000..8b5b986 --- /dev/null +++ b/cards/widgets.sh @@ -0,0 +1,287 @@ +# 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 . + +w_filter_item() { +n=$3 +cat <%s' \ + "$item" "$item" "$(pdi_value "$card" "$item" $c |pdi_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 |pdi_unescape |HTML)" "$(l10n "$item")" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + X-HEALTH-INSURANCE) + 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)" + IFS=\; read -r hi_name hi_number hi_status <<-EOF + $(pdi_value "$card" X-HEALTH-INSURANCE $c) + EOF + cat <<-EOF + [radio "$item" "list" .tab #hi_select_list checked][label for="hi_select_list" $(l10n hi_from_list)] + [radio "$item" "other" .tab #hi_other][label for="hi_other" $(l10n hi_other)] + [select .tab .item name="hi_company" + [option value="" disabled="disabled" $(selected "${hi_name}" "") . $(l10n hi_company)] + $(list_hi_companies |while read f; do + printf '[option value="%s" %s . %s]' "$(pdi_unescape "$f" |HTML)" \ + "$(selected "$f" "$hi_name")" \ + "$(pdi_unescape "$f" |HTML)" + done) + ] + [input type="text" .tab name="hi_other" value="$hi_name" placeholder="$(l10n hi_company)"] + [input type="text" name="hi_number" value="$(pdi_unescape "$hi_number" |HTML)" placeholder="$(l10n hi_number)"] + [input type="text" name="hi_status" value="$(pdi_unescape "$hi_status" |HTML)" placeholder="$(l10n hi_status)"] + EOF + done + ;; + *)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 |pdi_unescape |HTML)" "$(l10n "$item")" + done + printf '[button type="submit" name="action" value="addfield %s" %s ]' "$item" "$(l10n edit_addfield)" + ;; + esac + done +} diff --git a/cgilite.sh b/cgilite/cgilite.sh similarity index 100% rename from cgilite.sh rename to cgilite/cgilite.sh diff --git a/common.css b/cgilite/common.css similarity index 100% rename from common.css rename to cgilite/common.css diff --git a/file.sh b/cgilite/file.sh similarity index 100% rename from file.sh rename to cgilite/file.sh diff --git a/html-sh.sed b/cgilite/html-sh.sed similarity index 100% rename from html-sh.sed rename to cgilite/html-sh.sed diff --git a/logging.sh b/cgilite/logging.sh similarity index 100% rename from logging.sh rename to cgilite/logging.sh diff --git a/session.sh b/cgilite/session.sh similarity index 100% rename from session.sh rename to cgilite/session.sh diff --git a/storage.sh b/cgilite/storage.sh similarity index 100% rename from storage.sh rename to cgilite/storage.sh diff --git a/index.cgi b/index.cgi new file mode 100755 index 0000000..841917d --- /dev/null +++ b/index.cgi @@ -0,0 +1,83 @@ +#!/bin/sh + +for n in "$@"; do case ${n%%=*} in + data) _DATA="${n#data=}";; + exec) _EXEC="${n#exec=}";; + base) _BASE="${n#base=}";; + debug) DEBUG="${n#debug=}";; +esac; done + +[ ! "${_EXEC%/}" ] && _EXEC="$(realpath "${0%/*}")" || _EXEC="${_EXEC%/}" +[ ! "${_DATA%/}" ] && _DATA="${PWD%/}" || _DATA="${_DATA%/}" +_BASE="${_BASE%/}" +[ "$DEBUG" ] && exec 2>>"$DEBUG" + +mkdir -p "${_DATA}/cache" "${_DATA}/mappings" "${_DATA}/export" "${_DATA}/lock" "${_DATA}/ical" "${_DATA}/vcard" "${_DATA}/therapies" + +. "$_EXEC/cgilite/cgilite.sh" +. "$_EXEC/cgilite/session.sh" + +. "$_EXEC/l10n.sh" + +_PATH="$(PATH "/${PATH_INFO}")" +_PATH="${_PATH#${_BASE}}" +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 Lobster] + [meta name="viewport" content="width=device-width"] + [link rel="stylesheet" type="text/css" href="%s/cgilite/common.css"] + [link rel="stylesheet" type="text/css" href="%s/style.css"] + ' "${_BASE}" "${_BASE}" + [ -n "$style" ] && printf ' + [link rel="stylesheet" type="text/css" href="%s"] + ' "$style" + printf ' + ] [body #top class="%s" + ' "$class" + [ "$message" ] && printf '[p #message\n%s\n]' "$(l10n "$message")" + cat + printf '] ]' + } \ + | "${_EXEC}/cgilite/html-sh.sed" +} + +topdir="${_PATH#/}" +topdir="/${topdir%%/*}" + +case ${_PATH} in + "/") REDIRECT "${_BASE}/cards/" + ;; + *) + 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..66cafc7 --- /dev/null +++ b/l10n.sh @@ -0,0 +1,178 @@ +# 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";; + page_top) printf %s "⬆ Seitenanfang";; + + 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/pdiread.sh b/pdiread.sh new file mode 100755 index 0000000..58baa88 --- /dev/null +++ b/pdiread.sh @@ -0,0 +1,213 @@ +#!/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=' +' + +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_escape(){ + local in out='' + for in in "$@"; do + out="${out}${out:+;}" + while [ "$in" ]; do case $in in + \\*) out="${out}\\\\"; in="${in#\\}" ;; + ,*) out="${out}\\,"; in="${in#,}" ;; + \;*) out="${out}\\;"; in="${in#;}" ;; + "$BR"*) out="${out}\\n"; in="${in#${BR}}" ;; + *) out="${out}${in%%[\\,;${BR}]*}"; in="${in#"${in%%[\\,;${BR}]*}"}" ;; + esac; done + done + printf '%s\n' "$out" +} + +pdi_unescape(){ + local in out='' + [ $# -gt 0 ] && in="$*" || in="$(cat)" + while [ "$in" ]; do case $in in + \\\\*) out="${out}\\"; in="${in#\\\\}" ;; + \\n*) out="${out}${BR}"; in="${in#\\n}" ;; + \\*) in="${in#\\}" ;; + *) out="${out}${in%%\\*}"; in="${in#"${in%%\\*}"}" ;; + esac; done + printf '%s\n' "$out" +} + +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/prescriptions/edit_prescription.sh b/prescriptions/edit_prescription.sh new file mode 100755 index 0000000..b7a9af4 --- /dev/null +++ b/prescriptions/edit_prescription.sh @@ -0,0 +1,175 @@ +# 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/prescriptions/new_prescription.sh b/prescriptions/new_prescription.sh new file mode 100755 index 0000000..26173af --- /dev/null +++ b/prescriptions/new_prescription.sh @@ -0,0 +1,38 @@ +#!/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 . + +cgi_refdata + +client="${_POST[client]:-${_GET[client]}}" + +uid=$(uuidgenerator) +prescription="${client%.vcf}.${uid}.mpx" + +cardfile="$_DATA/vcard/$client" +tempfile="$_DATA/temp/$prescription" + +cat >"$tempfile" <. +*/ + +@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/prescriptions/prescriptions.html.sh b/prescriptions/prescriptions.html.sh new file mode 100755 index 0000000..c4ef19a --- /dev/null +++ b/prescriptions/prescriptions.html.sh @@ -0,0 +1,47 @@ +# 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 . + +client="${_GET[client]}" +edit="${_GET[edit]}" +[ -n "$edit" ] && client="${edit%.*.mpx}.vcf" + +cat <$(view_card "$client") + + + +
+
+ + +
+
+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: diff --git a/prescriptions/prescriptions.sh b/prescriptions/prescriptions.sh new file mode 100755 index 0000000..fcef09a --- /dev/null +++ b/prescriptions/prescriptions.sh @@ -0,0 +1,73 @@ +#!/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/prescriptions/text_prescriptions.sh b/prescriptions/text_prescriptions.sh new file mode 100755 index 0000000..8934bc7 --- /dev/null +++ b/prescriptions/text_prescriptions.sh @@ -0,0 +1,66 @@ +# 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/prescriptions/update_prescription.sh b/prescriptions/update_prescription.sh new file mode 100755 index 0000000..aeb4993 --- /dev/null +++ b/prescriptions/update_prescription.sh @@ -0,0 +1,55 @@ +#!/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/prescriptions/view_prescription.sh b/prescriptions/view_prescription.sh new file mode 100755 index 0000000..ced7241 --- /dev/null +++ b/prescriptions/view_prescription.sh @@ -0,0 +1,121 @@ +# 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 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/style.css b/style.css new file mode 100644 index 0000000..8644f0b --- /dev/null +++ b/style.css @@ -0,0 +1,337 @@ +h1:first-child, h2:first-child, h3:first-child, +p + h1, p + h2, p + h3 { + margin-top: 3pt; +} + +/* ====== COMMON ELEMENTS ======*/ + +#message { + display: block; + position: fixed; + top: 0; width: 100%; + margin: 0; padding: 1em; + text-align: center; + font-weight: bold; + background-color: #FAA; + border: 1px solid #000; + border-style: none none solid 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=text] { 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 { + position: sticky; + top: 0; + background-color: #FFF; + z-index: 2; +} +body.cards form.newcard input[name=seed] { flex: 1; } +body.cards form.newcard a[href="#top"] { float: right; } + + +/* ============ 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, Clients ======= */ + +div.card .therapies a.therapy.button { padding: .25em; line-height: .75em; } + +form.card .insurance #hi_select_list:checked ~ .tab[name=hi_company] {display: block; } +form.card .insurance #hi_other:checked ~ .tab[name=hi_other] {display: block; } + +/* ======= 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/therapies/autosave.js b/therapies/autosave.js new file mode 100644 index 0000000..665a601 --- /dev/null +++ b/therapies/autosave.js @@ -0,0 +1,50 @@ +var button = document.querySelector('#savebutton'); +var formdata_old = ''; +var formdata = ''; + +function postsubmit(){ + if ( this.status == 200 ) { + document.querySelector('#report input[name="tid"]').setAttribute('value', this.response); + console.log('successful auto submit of form data'); + button.setAttribute('style', 'display: none;'); + } else { + console.log('!!! Error response while auto submitting form data'); + button.setAttribute('style', 'display: block;'); + } +} +function failsubmit(){ + console.log('!!! Timeout while auto submitting form data'); + button.setAttribute('style', 'display: block;'); +} + +function formencode(fd){ + var send; + send='autosubmit=true'; + for (var tup of fd.entries()){ + send += '&' + encodeURIComponent(tup[0]) + '=' + encodeURIComponent(tup[1]); + } + return send; +} + +function formsend(){ + var request = new XMLHttpRequest(); + request.open('post', '/therapies/update_therapy.sh'); + request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + request.timeout = 5000; + request.onload = postsubmit; + request.ontimeout = failsubmit; + request.onerror = failsubmit; + request.onabort = failsubmit; + + formdata = formencode(new FormData(document.querySelector('#report'))); + + if ( ! (formdata == formdata_old) ) { + console.log( 'send' ); + request.send( formdata ); + formdata_old = formdata; + } +} + +formdata_old = formencode(new FormData(document.querySelector('#report'))); +button.setAttribute('style', 'display: none;'); +setInterval(formsend, 500); diff --git a/therapies/index.cgi b/therapies/index.cgi new file mode 100755 index 0000000..515f2b0 --- /dev/null +++ b/therapies/index.cgi @@ -0,0 +1,69 @@ +#!/bin/sh + +# Copyright 2016, 2020 Paul Hänsch +# +# This file is part of Lobster. +# +# Lobster 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. +# +# Lobster 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 Lobster. If not, see . + +case "$_PATH" in + /therapies/therapy.css) + . $_EXEC/cgilite/file.sh + FILE $_EXEC/therapies/therapy.css + return 0 + ;; + /therapies/*.*_session*.png) + if [ -r "$_DATA/$_PATH" ]; then + . $_EXEC/cgilite/file.sh + FILE "$_DATA/$_PATH" + fi + return 0 + ;; + /therapies/*/new) + card="${_PATH#/therapies/}" + card="${card%/new}" + uid="$(timeid)$(randomid)" + if [ -f "${_DATA}/vcard/${card}.vcf" ]; then + touch "${_DATA}/therapies/${card}.${uid}.tpy" + rm -f "${_DATA}/cache/${card}.vcf.cache" + REDIRECT "/therapies/${card}/${uid}" + fi + return 0 + ;; +esac + +. $_EXEC/pdiread.sh +. $_EXEC/therapies/l10n.sh + +id="${_PATH#/therapies/}" +id="${id%/*}.${id#*/}" + +read junkx junky bg_dim junkz <<-E_READ + $(identify "$_EXEC/therapies/therapy_background.png") +E_READ + +vcffile="${_DATA}/vcard/${id%.*}.vcf" +mpxfile="${_DATA}/prescriptions/${id}.mpx" +tpyfile="${_DATA}/therapies/${id}.tpy" + +vcf="$(pdi_load "$vcffile")" +mpx="$([ -f "$mpxfile" ] && sed '1s;^;\n;; s/:/;:/' "$mpxfile")" +tpy="$(sed '1s;^;\n;; s/:/;:/' "$tpyfile")" + +VCF(){ pdi_value "$vcf" "$@"; } +MPX(){ pdi_value "$mpx" "$@"; } +TPY(){ pdi_value "$tpy" "$@" >/dev/null && pdi_value "$tpy" "$@" |pdi_unescape; } + +. $_EXEC/therapies/page.sh \ +| yield_page therapy /therapies/therapy.css diff --git a/therapies/l10n.sh b/therapies/l10n.sh new file mode 100755 index 0000000..167dc4d --- /dev/null +++ b/therapies/l10n.sh @@ -0,0 +1,70 @@ +# Copyright 2016, 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 . + +#. $_EXEC/templates/text_prescriptions.sh +l10n(){ + local word + [ $# -eq 0 ] && read -r word || word="$1" + case $word in + therapy_prescription) printf '%s\n' "Heil­mit­tel­ver­ord­nung";; + prescriptions_current) printf '%s\n' "Aktuelle Verordnungen";; + prescriptions_past) printf '%s\n' "Frühere Verordnungen";; + newprescription) printf '%s\n' "Neue Verordnung";; + date) printf '%s\n' "Datum";; + name) printf '%s\n' "Name d. Versicherten";; + bday) printf '%s\n' "geb. am";; + addcontrib) printf '%s\n' "Zuzahlung";; + contribconfirm) printf '%s\n' "Zuzahlung erfolgt am...";; + contribreceipt) printf '%s\n' "Quit­tung heraus­ge­ge­ben";; + prescreviewed) printf '%s\n' "Verordnung geprüft";; + prescreview) printf '%s\n' "Verordnung prüfen!";; + quantity) printf '%s\n' "Ver­ord­nungs­men­ge";; + remidy) printf '%s\n' "Heil­mit­tel nach Maß­ga­be des Ka­ta­lo­ges";; + prescfirst) printf '%s\n' "Erst­ver­ord­nung";; + prescfollow1) printf '%s\n' "1. Folge-­VO";; + prescfollow2) printf '%s\n' "2. Folge-­VO";; + prescother) printf '%s\n' "VO außer­halb des Re­gel­falls";; + presccontinual) printf '%s\n' "Lang­frist­ver­ord­nung";; + grouptherapy) printf '%s\n' "Grup­pen­the­ra­pie";; + housecall) printf '%s\n' "Haus­be­such";; + report) printf '%s\n' "The­ra­pie­be­richt";; + indicator) printf '%s\n' "In­di­ka­tions­schlüssel";; + icd10) printf '%s\n' "ICD-10-Code";; + indicator_reading) printf '%s\n' "Befund Beschreibung";; + insurance) printf '%s\n' "Krankenkasse bzw. Kostenträger";; + prescription_by_catalogue) printf '%s\n' "Verordnung nach Maßgabe des Kataloges (Regelfall)";; + therapy_start) printf '%s\n' "Be­hand­lungs­be­ginn spä­test. am";; + quantity_weekly) printf '%s\n' "An­zahl pro Wo­che";; + save) printf '%s\n' "Speichern";; + cancel) printf '%s\n' "Abbrechen";; + delete) printf '%s\n' "Löschen";; + + therapy) printf '%s\n' "Therapie";; + therapies) printf '%s\n' "Therapien";; + + prescriptionlist) printf '%s\n' "Zur Verordnungsliste";; + delete_session) printf '%s\n' "Therapiesitzung entfernen";; + therapist) printf '%s\n' "Therapeut";; + number) printf '%s\n' "Nr.";; + signature) printf '%s\n' "Un­ter­schrift";; + weekly) printf '%s\n' "p. Woche";; + notes) printf '%s\n' "Notizen";; + trailsave) printf '%s\n' "Speichern für weitere Felder";; + + *) HTML "$word";; + esac +} diff --git a/therapies/page.sh b/therapies/page.sh new file mode 100755 index 0000000..a53bb92 --- /dev/null +++ b/therapies/page.sh @@ -0,0 +1,228 @@ +# Copyright 2016, 2017, 2020 Paul Hänsch +# +# This file is part of Lobster. +# +# 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. +# +# Lobster 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 Lobster. If not, see . + +t_session_note(){ + session_n="$1" + note_n="$2" + + color=session${session_n}_note${note_n}_color + + if [ "$note_n" -eq 1 -o "$(TPY "session${session_n}_note${note_n}")" ]; then + printf '[input type="checkbox" .trailbtn checked="checked"]' + else + printf '[input type="checkbox" .trailbtn]' + fi + + check="$(TPY "$color" |grep -xE -m1 '#(888|00A|0A0|0AA|A00|A0A|AA0)' || printf '#FFF')" + cat <<-EOF + [fieldset .note .trailbox + [radio "$color" "#888" .color #${color}_000 $(checked "$check" '#888') ][label for=${color}_000] + [radio "$color" "#00A" .color #${color}_001 $(checked "$check" '#00A') ][label for=${color}_001] + [radio "$color" "#0A0" .color #${color}_010 $(checked "$check" '#0A0') ][label for=${color}_010] + [radio "$color" "#0AA" .color #${color}_011 $(checked "$check" '#0AA') ][label for=${color}_011] + [radio "$color" "#A00" .color #${color}_100 $(checked "$check" '#A00') ][label for=${color}_100] + [radio "$color" "#A0A" .color #${color}_101 $(checked "$check" '#A0A') ][label for=${color}_101] + [radio "$color" "#AA0" .color #${color}_110 $(checked "$check" '#AA0') ][label for=${color}_110] + [radio "$color" "#FFF" .color #${color}_111 $(checked "$check" '#FFF') ][label for=${color}_111] + [textarea name="session${session_n}_note${note_n}" . $(TPY "session${session_n}_note${note_n}" |HTML)] + ] + EOF +} + +therapy_sessions(){ + n=1; while [ "$(TPY session$n)" ]; do n=$((n + 1)); done + + sid=session$n + cat <<-EOF + [fieldset .tab + [submit "new_session" "$sid" .no . +] + [input .date name="${sid}_date" value="" placeholder=$(l10n date)] + [input .therapist name="${sid}_therapist" value="" placeholder=$(l10n therapist)] + [span .signature] + [hidden "${sid}_note1" ""] + ] + EOF + + for n in $(seq $((n - 1)) -1 1); do + session_n="$n" + sid=session${session_n} + + cat <<-EOF + [hidden "$sid" "exists"] + [checkbox "${sid}_open" "checked" #${sid}_open .tab $(checked "$(TPY "${sid}_open")" checked)] + [label .tab for="${sid}_open" + [span .no ${session_n}.] + [span .date . $(TPY "${sid}_date" |HTML)] + [input .date name="${sid}_date" value="$(TPY "${sid}_date" |HTML)" placeholder="$(l10n date)"] + [span .therapist . $(TPY "${sid}_therapist" |HTML)] + [input .therapist name="${sid}_therapist" value="$(TPY "${sid}_therapist" |HTML)" placeholder="$(l10n therapist)"] + [span .signature [checkbox "${sid}_sigset" "pos" $(checked "$(TPY "${sid}_sigset")" "pos")]] + ] + [div .tab + [img .dotmark .ov src="/therapies/${id}_${sid}.png?${_DATE}" alt=""] + $(n=1; while TPY "session${session_n}_note${n}" >/dev/null; do + [ "$(TPY "session${session_n}_note${n}")" ] && x=$n + n=$(($n + 1)) + done + for n in $(seq 1 $((${x:-0} + 3)) ); do + t_session_note $session_n $n + done + ) + [button type="submit" .trailbtn $(l10n trailbtn)] + [button type="submit" name=delete_session value="$session_n" .delete $(l10n delete_session)] + ] + EOF + done +} + +cat < 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; +} + +form#report { + position: static; + width: 100%; + margin:0; padding: 0; + box-shadow: none; +} + +form#report button#backbutton { + display: inline; + position: absolute; + top: 1.75em; left: 2%; + font-size: 1.125em; + background: transparent; + padding: 0; + border: none; box-shadow: none; +} + +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, div.therapies { + display: block; + width: 96%; + margin: 0 2%; +} + +div.patient a { + text-decoration: none; color: #000; +} + +div.therapies > a { + display: inline-block; + text-decoration: none; + border: 1px solid black; + margin-top: .5em; padding: .25em .5em; + background-color: #DDF; + color: #000; +} +div.therapies > a.current { + background-color: #AAF; +} + +div.prescription { + background-color: #CFF; +} +div.prescription h3 { + margin-top: .5em; +} + +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: .5em; + margin-left: 1.5em; +} + +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 > 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 + .stickynote > label { + position: absolute; + top: 0; bottom: 0; right: .5ex; left: 0; + display: block; + text-align: right; + font-weight: bold; + z-index: 1; +} +input.stickynote + .stickynote:hover { + width: auto; left: 0em; right: 2em; +} +input.stickynote:checked + .stickynote { + width: auto; left: 1em; right: 1em; +} +input.stickynote:checked + .stickynote > * { display: block; } +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; + line-height: 1em; + 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-bottom: .375em; + border: none; + padding: 0; +} +fieldset.penwidth { bottom: 17em; } +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, div.therapies { + width: 38%; + margin-right: 0; + } + input.stickynote + .stickynote:hover { right: calc(50% + 1em); } + 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, div.therapies { margin-top: .5em; } +div.prescription, div.therapies { + border: 1px solid black; + 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%; width: calc(100% - 1.25em); + height: 8em; + margin: -8em 0 .5em 1.25em; + 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/therapies/therapy_background.png b/therapies/therapy_background.png new file mode 100644 index 0000000..a0574d5 Binary files /dev/null and b/therapies/therapy_background.png differ diff --git a/therapies/therapy_background.xcf b/therapies/therapy_background.xcf new file mode 100644 index 0000000..e69c024 Binary files /dev/null and b/therapies/therapy_background.xcf differ diff --git a/therapies/therapy_draw.js b/therapies/therapy_draw.js new file mode 100644 index 0000000..19542c8 --- /dev/null +++ b/therapies/therapy_draw.js @@ -0,0 +1,119 @@ +// Copyright 2016, 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 . + +body = document.body +dbg = document.getElementById("jsdebug") +canvas = document.getElementById("canvas") +data = document.getElementById("image_serialize") + +image = canvas.getContext("2d") +mouse = 0 +image_serialize="" +image.lineJoin = "round" +image.lineCap = "round" +data.value += " stroke-linejoin round " +data.value += " stroke-linecap round " + +// 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 " + stx + "," + sty; +} + +function drawstop() { + + // if path ends close to beginning ( < 50 px); then close path and fill + if ( 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/therapies/update_therapy.sh b/therapies/update_therapy.sh new file mode 100755 index 0000000..e084e6d --- /dev/null +++ b/therapies/update_therapy.sh @@ -0,0 +1,132 @@ +#!/bin/zsh + +# Copyright 2016, 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 . + +. "$_EXEC/pdiread.sh" +tpy="$(POST id)" + +tpyfile="$_DATA/therapies/${tpy}.tpy" +tempfile="$_DATA/lock/${tpy}.$$.tpy" + +if [ "$(POST tid)" != "$(transid "$tpyfile")" ]; then + if [ "$(POST autosubmit)" = "true" ]; then + printf 'Status: 409 Conflict\r\nContent-Length: 0\r\n\r\n' + exit 0 + else + SET_COOKIE session message="TRANSACTION_CONFLICT" + REDIRECT "/therapies/${tpy%.*}/${tpy#*.}" + fi +fi + +if [ "$(POST formend)" != "formend" ]; then + if [ "$(POST autosubmit)" = "true" ]; then + printf 'Status: 409 Conflict\r\nContent-Length: 0\r\n\r\n' + exit 0 + else + SET_COOKIE session message="INCOMPLETE_SUBMIT" + REDIRECT "/therapies/${tpy%.*}/${tpy#*.}" + fi +fi + +# serialize POST array into file +for key in $(POST_KEYS); do + case "$key" in + imagedata|tid|formend) : ;; + session*_date) + value="$(POST "$key")" + y=0 mon=0 dom=0 + case $value in + *.*.*) IFS=. read dom mon y <<-END + ${value} + END + ;; + *.*.) IFS=. read dom mon <<-END + ${value} + END + ;; + */*/*) IFS=/ read mon dom y <<-END + ${value} + END + ;; + */*) IFS=/ read mon dom <<-END + ${value} + END + ;; + *-*-*) IFS=- read y mon dom <<-END + ${value} + END + ;; + esac + [ ! "$y" ] && y="$(date +%Y)" + [ "$y" -gt 0 -a "$y" -lt 100 ] && y="$((y + 2000))" + date -d "${y}-${mon}-${dom}" + && printf %s:%s\\n "$key" "$(date -d "${y}-${mon}-${dom}" +%F)" \ + || printf %s:\\n "$key" + ;; + *) printf %s:%s\\n "$key" "$(pdi_escape "$(POST "$key")")" ;; + esac +done >"$tempfile" 2>&- + +if [ "$(POST delete_session)" ]; then + n="$(POST delete_session)" + sed -Ei '/^session'$n'[_:]/d' "$tempfile" + rm "${tpyfile%.tpy}_session${n}.png" + + while grep -Eq '^session'$(($n + 1))'_' "$tempfile"; do + sed -Ei '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 [ "$(POST new_session)" ]; then + sid="$(POST new_session)" + + read junkx junky dim junkz <<-E_READ + $(identify "$_EXEC/therapies/therapy_background.png") + E_READ + + convert -size "$dim" xc:transparent "${tpyfile%.tpy}_${sid}.png" + + printf '%s:exists\n' "$sid" >>"$tempfile" + printf '%s_open:checked\n' "$sid" >>"$tempfile" + +elif [ "$(POST imagedata)" ]; then + sid="$(sed -En 's;^(session[0-9]+)_open:checked$;\1;p' "$tempfile" \ + | sort -n \ + | tail -n1 + )" + + convert "${tpyfile%.tpy}_${sid}.png" \ + -draw "$(POST imagedata)" -transparent white \ + "${tpyfile%.tpy}_${sid}.png" + sync +fi + +if ! diff -q "$tempfile" "$tpyfile" >/dev/null; then + mv "$tempfile" "$tpyfile" + rm -f -- "${_DATA}/cache/${tpy%%.*}.vcf.cache" +fi + +if [ "$(POST autosubmit)" = "true" ]; then + msg="$(transid "$tpyfile")" + printf 'HTTP/1.1 200 OK\r\nContent-Length: %i\r\n\r\n%s' \ + "${#msg}" "${msg}" +elif [ "$(POST vcfreturn)" ]; then + REDIRECT "/cards/#${tpy%.*}.vcf" +else + REDIRECT "/therapies/${tpy%.*}/${tpy#*.}" +fi diff --git a/update_bookmarks.sh b/update_bookmarks.sh new file mode 100755 index 0000000..1670840 --- /dev/null +++ b/update_bookmarks.sh @@ -0,0 +1,33 @@ +#!/bin/zsh + +# Copyright 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 . + +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"