--- /dev/null
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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
+<http://www.gnu.org/licenses/>.
--- /dev/null
+.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
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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"
--- /dev/null
+#!/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
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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
--- /dev/null
+#!/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
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+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 "";;
--- /dev/null
+#!/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
+}
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+. "$_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
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+w_filter_item() {
+n=$3
+cat <<EOF
+ [fieldset .item
+ [legend $(l10n filter_item):]
+
+ $(for field in any name street zip TEL BDAY; do
+ printf '[input id="%s%i" type="radio" .tab name="filter_type%i" value="%s" %s]
+ [label for="%s%i" %s ]' \
+ "$field" "$n" "$n" "$field" "$([ "$1" = "$field" ] && printf checked )" \
+ "$field" "$n" "$(l10n filter_$field)"
+ done)
+ [input type="text" .tab name="filter_text$n" value="$([ "$1" = CATEGORIES ] || HTML "$2")" placeholder="$(l10n filter_placeholder)"]
+ ]
+EOF
+}
+
+w_filter_diag(){
+ cat <<EOF
+ [form .filter action="/cards/filter_card.sh" method="POST"
+ [h1 $(l10n filter_label)]
+ [input type="hidden" name="page" value="cards"]
+
+ $(n=0; filter="${filter}^"
+ while [ "${filter#^}" ]; do
+ fil="${filter%%^*}" filter="${filter#*^}"
+ w_filter_item "${fil%%:*}" "${fil#*:}" $n
+ n=$((n + 1))
+ done
+ [ "$n" -eq 0 -o "$(GET newfilter)" ] && w_filter_item any '' $n
+ )
+ [button type="submit" name="choice" value="new_filter" $(l10n filter_more)]
+ [fieldset class="order"
+ [legend $(l10n filter_order):]
+ [label [radio "order" "lastname" $( [ "$order" = lastname ] && printf checked )] $(l10n filter_lastname)]
+ [label [radio "order" "firstname" $( [ "$order" = firstname ] && printf checked )] $(l10n filter_firstname)]
+ [label [radio "order" "bdate" $( [ "$order" = bdate ] && printf checked )] $(l10n filter_bdate)]
+ ]
+ [button type="submit" name="choice" value="filter" $(l10n filter_apply)]
+ [button type="submit" name="choice" value="del_filter" $(l10n filter_cancel)]
+ [button type="submit" name="choice" value="export_csv" $(l10n export_csv)]
+ ]
+EOF
+}
+
+card_item(){
+ local card="$1"
+ local item cnt c
+ shift 1
+
+ for item in $@; do
+ cnt="$(pdi_count "$card" "$item")"
+
+ case $item in
+ FN) printf '[h2 .item .FN . %s]' "$(pdi_value "$card" FN |pdi_unescape |HTML)"
+ ;;
+ GENDER) printf '[span .item .GENDER . %s]' "$(pdi_value "$card" GENDER |l10n)"
+ ;;
+ NICKNAME) seq 1 $cnt |while read c; do
+ printf '[span .item .NICKNAME . aka. "%s"]' \
+ "$(pdi_value "$card" NICKNAME $c |pdi_unescape |HTML)"
+ done
+ ;;
+ X-ZACK-JOINDATE|X-ZACK-LEAVEDATE) if [ $cnt -gt 0 ]; then
+ printf '[span .item .%s [b %s:] %s]' \
+ "$item" "$(l10n "${item}_short")" \
+ "$(pdi_value "$card" "$item" |HTML)"
+ fi
+ ;;
+ BDAY)
+ [ $cnt -gt 0 ] && printf '[span .item .BDAY [b *:] %s]' \
+ "$(pdi_value "$card" BDAY |grep -xE '[0-9-]+')"
+ ;;
+ SOUND)
+ [ $cnt -gt 0 ] && printf '[audio .item .SOUND controls="controls"
+ [source type="audio/ogg" src="data:audio/ogg;base64,%s"]
+ ]' \
+ "$(pdi_value "$card" SOUND |grep -xE '[a-zA-Z0-9/+=]+')"
+ ;;
+ PHOTO|LOGO)
+ [ $cnt -gt 0 ] && printf '[img .item .%s src="data:image/%s;base64,%s"]' "$item" \
+ "$(pdi_attrib "$card" "$item" |sed -E 's;^(.*;)?TYPE="?(.+)"?(;.*)?$;\2;')" \
+ "$(pdi_value "$card" "$item" |grep -xE '[a-zA-Z0-9/+=]+')"
+ ;;
+ EMAIL)
+ [ $cnt -gt 0 ] && printf '[h3 %s]' "$(l10n EMAIL)"
+ seq 1 $cnt |while read c; do
+ printf '[a .item .EMAIL href="mailto:%s" . %s]' \
+ "$(pdi_value "$card" EMAIL $c |pdi_unescape |HTML)" \
+ "$(pdi_value "$card" EMAIL $c |pdi_unescape |HTML)"
+ done
+ ;;
+ TEL)
+ [ $cnt -gt 0 ] && printf '[h3 %s]' "$(l10n TEL)"
+ seq 1 $cnt |while read c; do
+ teltype="$(pdi_attrib "$card" TEL $c TYPE)"
+ [ "$teltype" ] \
+ && printf '[span .item .TEL [span .type . %s:] %s]' \
+ "$(l10n "TYPE=$teltype" |HTML)" \
+ "$(pdi_value "$card" TEL $c |pdi_unescape |HTML)" \
+ || printf '[span .item .TEL . %s]' \
+ "$(pdi_value "$card" TEL $c |pdi_unescape |HTML)"
+ done
+ ;;
+ X-HEALTH-INSURANCE)
+ [ $cnt -gt 0 ] && printf '[h3 %s]' "$(l10n X-HEALTH-INSURANCE)"
+ seq 1 $cnt |while read c; do
+ IFS=\; read -r hi_name hi_number hi_status <<-EOF
+ $(pdi_value "$card" X-HEALTH-INSURANCE $c)
+ EOF
+ printf '[span .item .hi_company . %s]
+ [span .item .hi_number [label %s:] %s]
+ [span .item .hi_status [label %s:] %s]
+ ' "$(pdi_unescape "$hi_name" |HTML)" \
+ "$(l10n hi_number)" "$(pdi_unescape "$hi_number" |HTML)" \
+ "$(l10n hi_status)" "$(pdi_unescape "$hi_status" |HTML)"
+ done
+ ;;
+ therapies)
+ client="$(pdi_value "$card" UID)"
+ printf '[h3 %s]' "$(l10n therapies)"
+ (cd "$_DATA/therapies/"; printf '%s\n' "${client}".*.tpy) \
+ | while read tpyfile; do
+ [ "$tpyfile" = "${client}.*.tpy" ] \
+ && printf '[a .button .therapy href="/therapies/%s/new" . +]' "${client}" \
+ && break
+ tpy="${tpyfile%.tpy}";
+ tpydates="$(sed -En 's;^session[0-9]+_date:;;p;' "$_DATA/therapies/$tpyfile" \
+ | sort \
+ | sed -E ':X;N;$!bX; s;^[\n ]+;;; s;[\n ]+$;;; s;(\n.*\n|\n); - ;;'
+ )"
+ printf '[a .item .therapy href="/therapies/%s" . %s] ' \
+ "${tpy%.*}/${tpy#*.}" "$(HTML "${tpydates:--}")"
+ done |sort -n
+ ;;
+ *)[ $cnt -gt 0 ] && printf '[h3 %s]' "$(l10n "$item")"
+ shy="$(printf '\302\255')"
+ seq 1 $cnt |while read c; do
+ printf '[span .item .%s . %s]' "$item" \
+ "$(pdi_value "$card" "$item" $c |sed -r "s;(straße|weg|damm|allee|ufer);${shy}\1;g" |pdi_unescape |HTML)"
+ done
+ ;;
+ esac
+ done
+}
+
+edit_item(){
+ local card="$1"
+ local item cnt c
+ shift 1
+
+ for item in $@; do
+ cnt="$(pdi_count "$card" "$item")"
+ [ $cnt -lt 1 ] && cnt=1
+
+ case $item in
+ N)N="$(pdi_value "$card" N)"
+ if [ "$N" ]; then
+ IFS=\; read n1 n2 n3 n4 n5 <<-EOF
+ $N
+ EOF
+ else
+ N="$(pdi_value "$card" FN |pdi_unescape)"
+ n1="${N##* }"
+ n2="${N%$n1}"
+ fi
+ printf '
+ [h3 %s]
+ [input .item .N name="4N" placeholder="%s" value="%s"]
+ [input .item .N name="2N" placeholder="%s" value="%s"]
+ [input .item .N name="1N" placeholder="%s" value="%s"]
+ [input .item .N name="5N" placeholder="%s" value="%s"]
+ ' "$(l10n "$item")" \
+ "$(l10n n_pre)" "$(HTML "$n4")" \
+ "$(l10n n_first)" "$(HTML "${n2}$([ "$n2" -a "$n3" ] && printf ' ')${n3}")" \
+ "$(l10n n_last)" "$(HTML "$n1")" \
+ "$(l10n n_post)" "$(HTML "$n5")"
+ ;;
+ GENDER)
+ gender="$(pdi_value "$card" GENDER)"
+ printf '
+ [select .item .GENDER name="GENDER"
+ [option value="" disabled="disabled" %s %s]
+ [option value="female" %s %s]
+ [option value="male" %s %s]
+ [option value="other" %s %s]
+ [option value="none" %s %s]
+ ]\n' \
+ "$([ "$gender" = '' ] && printf 'selected="selected"')" "$(l10n GENDER)" \
+ "$([ "$gender" = 'female' ] && printf 'selected="selected"')" "$(l10n gender_female)" \
+ "$([ "$gender" = 'male' ] && printf 'selected="selected"')" "$(l10n gender_male)" \
+ "$([ "$gender" = 'other' ] && printf 'selected="selected"')" "$(l10n gender_other)" \
+ "$([ "$gender" = 'none' ] && printf 'selected="selected"')" "$(l10n gender_none)"
+ ;;
+ BDAY|X-ZACK-JOINDATE|X-ZACK-LEAVEDATE)
+ printf '[h3 %s]
+ [input .item .%s name="%s" value="%s" placeholder="YYYY-MM-DD"]' \
+ "$(l10n "$item")" "$item" "$item" "$(pdi_value "$card" "$item" |grep -xE '[0-9-]+')"
+ ;;
+ ADR|NOTE)
+ 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 '<textarea class="item %s" name="%s">%s</textarea>' \
+ "$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
+}
--- /dev/null
+#!/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
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+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
+}
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+# 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#*:}"
+}
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+check(){ [ "$1" = "$2" ] && printf checked}
+[ -z $mpx[presctype] ] && mpx[presctype]=doctor_compulsory
+
+cat <<END_HTML
+<form class="prescription" action="?action=update_prescription" method="POST">
+<input type="hidden" name="prescription" value="${mpx[prescription]}">
+
+<label class=presctype>$(l10n doctor):</label>
+<input type=radio name=presctype id=doctor_selfpaid value=doctor_selfpaid $(check "$mpx[presctype]" doctor_selfpaid)>
+<label for=doctor_selfpaid>$(l10n selfpaid)</label>
+<input type=radio name=presctype id=doctor_private value=doctor_private $(check "$mpx[presctype]" doctor_private)>
+<label for=doctor_private>$(l10n private)</label>
+<input type=radio name=presctype id=doctor_compulsory value=doctor_compulsory $(check "$mpx[presctype]" doctor_compulsory)>
+<label for=doctor_compulsory>$(l10n compulsory)</label>
+<br>
+
+<label class=presctype>$(l10n dentist):</label>
+<input type=radio name=presctype id=dentist_selfpaid value=dentist_selfpaid $(check "$mpx[presctype]" dentist_selfpaid)>
+<label for=dentist_selfpaid>$(l10n selfpaid)</label>
+<input type=radio name=presctype id=dentist_private value=dentist_private $(check "$mpx[presctype]" dentist_private)>
+<label for=dentist_private>$(l10n private)</label>
+<input type=radio name=presctype id=dentist_compulsory value=dentist_compulsory $(check "$mpx[presctype]" dentist_compulsory)>
+<label for=dentist_compulsory>$(l10n compulsory)</label>
+<br>
+
+<label class=presctype>$(l10n noprescription):</label>
+<input type=radio name=presctype id=noprescription_selfpaid value=noprescription_selfpaid $(check "$mpx[presctype]" noprescription_selfpaid)>
+<label for=noprescription_selfpaid>$(l10n selfpaid)</label>
+<br>
+
+<label class=presctype>$(l10n altpractition):</label>
+<input type=radio name=presctype id=altpractition_selfpaid value=altpractition_selfpaid $(check "$mpx[presctype]" altpractition_selfpaid)>
+<label for=altpractition_selfpaid>$(l10n selfpaid)</label>
+<input type=radio name=presctype id=altpractition_private value=altpractition_private $(check "$mpx[presctype]" altpractition_private)>
+<label for=altpractition_private>$(l10n private)</label>
+<br>
+
+<fieldset class="baseinfo">
+<label for="insurance">$(l10n insurance)</label>
+<input id="insurance" name="insurance" value="${mpx[insurance]}" placeholder="$(l10n insurance)">
+<br>
+<label for="name">$(l10n name)</label>
+<label for="bday">$(l10n bday)</label>
+<br>
+<textarea id="name" name="name" placeholder="$(l10n name)">${mpx[name]}</textarea>
+<input id="bday" name="bday" value="${mpx[bday]}" placeholder="$(l10n bday)">
+
+<br>
+<label for="date">$(l10n date)</label>
+<input id="date" name="date" value="${mpx[date]}" placeholder="$(l10n date)">
+</fieldset>
+
+<fieldset class="misc">
+<h1 id="${mpx[prescription]}">$(l10n therapy_prescription)</h1>
+
+<label for="addcontrib">$(l10n addcontrib)</label>
+<input id="addcontrib" name="addcontrib" value="${mpx[addcontrib]}" placeholder="$(l10n addcontrib)">
+<label for="contribconfirm">$(l10n contribconfirm)</label>
+<input id="contribconfirm" name="contribconfirm" value="${mpx[contribconfirm]}" placeholder="$(l10n contribconfirm)">
+<input type="checkbox" id="contribreceipt" name="contribreceipt" value="true" ${mpx[contribreceipt]:+checked}>
+<label for="contribreceipt">$(l10n contribreceipt)</label>
+</fieldset>
+
+<input type="checkbox" id="prescreviewed" name="prescreviewed" value="true" ${mpx[prescreviewed]:+checked}>
+<label for="prescreviewed">$(l10n prescreviewed)</label>
+
+<fieldset class="catalogue">
+<h2>$(l10n prescription_by_catalogue)</h2>
+<input type="radio" id="prescfirst" name="prescno" value="first" $(check "$mpx[prescno]" first)>
+<label for="prescfirst">$(l10n prescfirst)</label>
+<br>
+<input type="radio" id="prescfollow1" name="prescno" value="follow1" $(check "$mpx[prescno]" follow1)>
+<label for="prescfollow1">$(l10n prescfollow1)</label>
+<br>
+<input type="radio" id="prescfollow2" name="prescno" value="follow2" $(check "$mpx[prescno]" follow2)>
+<label for="prescfollow2">$(l10n prescfollow2)</label>
+<br>
+<input type="radio" id="prescother" name="prescno" value="other" $(check "$mpx[prescno]" other)>
+<label for="prescother">$(l10n prescother)</label>
+<br>
+<input type="radio" id="presccontinual" name="prescno" value="continual" $(check "$mpx[prescno]" continual)>
+<label for="presccontinual">$(l10n presccontinual)</label>
+
+<br>
+<input type="checkbox" id="grouptherapy" name="grouptherapy" value="true" ${mpx[grouptherapy]:+checked}>
+<label for="grouptherapy">$(l10n grouptherapy)</label>
+<br>
+<input type="checkbox" id="housecall" name="housecall" value="true" ${mpx[housecall]:+checked}>
+<label for="housecall">$(l10n housecall)</label>
+<br>
+<input type="checkbox" id="report" name="report" value="true" ${mpx[report]:+checked}>
+<label for="report">$(l10n report)</label>
+</fieldset>
+
+<fieldset class="description">
+<label for="quantity">$(l10n quantity)</label>
+<label for="remidy">$(l10n remidy)</label>
+<label for="quantity_weekly">$(l10n quantity_weekly)</label>
+<p>
+<input id="quantity" name="quantity" value="${mpx[quantity]}" placeholder="$(l10n quantity)">
+<textarea id="remidy" name="remidy" placeholder="$(l10n remidy)">${mpx[remidy]}</textarea>
+<input id="quantity_weekly" name="quantity_weekly" value="${mpx[quantity_weekly]}" placeholder="$(l10n quantity_weekly)">
+</p>
+
+$( 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 '<input class="trailbtn" type="checkbox" checked="checked" />'
+ else
+ printf '<input class="trailbtn" type="checkbox" />'
+ fi
+ printf '<p class="trailbox">
+ <input class="quantity" name="quantity" placeholder="%s" value="%s">
+ <textarea class="remidy" name="remidy" placeholder="%s">%s</textarea>
+ <input class="quantity_weekly" name="quantity_weekly" placeholder="%s" value="%s">
+ </p>
+ ' "$(l10n quantity)" "${mpx[quantity$n]}" \
+ "$(l10n remidy)" "${mpx[remidy$n]}" \
+ "$(l10n quantity_weekly)" "${mpx[quantity_weekly$n]}"
+done )
+
+<br>
+<p class="indicator_codes">
+<label for="indicator">$(l10n indicator)</label>
+<input id="indicator" name="indicator" value="${mpx[indicator]}" placeholder="$(l10n indicator)">
+<br>
+<label for="icd10">$(l10n icd10)</label>
+<input id="icd10" name="icd10" value="${mpx[icd10]}" placeholder="$(l10n icd10)">
+</p>
+<br>
+<p class="indicator_reading">
+<label for="indicator_reading">$(l10n indicator_reading)</label>
+<textarea id="indicator_reading" name="indicator_reading" placeholder="$(l10n indicator_reading)">${mpx[indicator_reading]}</textarea>
+</p>
+<br>
+<p class="issuer">
+ <label>$(l10n issuer)</label>
+ <input type="radio" name="issuer_select" value="list" id="issuer_select_list" checked /><label for="issuer_select_list">$(l10n issuer_from_list)</label><!--
+ --><input type="radio" name="issuer_select" value="other" id="issuer_other"><label for="issuer_other">$(l10n issuer_other)</label>
+ <select class="item" name="issuer">
+ <option value="" disabled="disabled" $([ -z "${mpx[issuer]}" ] && printf 'selected' )>$(l10n issuer)...</option>
+ $(list_prescription_issuers |while read f; do
+ [ "$f" = "$mpx[issuer]" ] \
+ && printf '<option value="%s" selected>%s</option>' "$f" "$f" \
+ || printf '<option value="%s">%s</option>' "$f" "$f"
+ done)
+ </select>
+ <input type="text" name="issuer_other" value="" placeholder="$(l10n issuer)..." />
+</p>
+</fieldset>
+
+<fieldset class="controls">
+<button type="submit" name="action" value="save">$(l10n save)</button>
+<button type="submit" name="action" value="cancel">$(l10n cancel)</button>
+<button type="submit" name="action" value="delete">$(l10n delete)</button>
+</fieldset>
+
+</form>
+END_HTML
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+cgi_refdata
+
+client="${_POST[client]:-${_GET[client]}}"
+
+uid=$(uuidgenerator)
+prescription="${client%.vcf}.${uid}.mpx"
+
+cardfile="$_DATA/vcard/$client"
+tempfile="$_DATA/temp/$prescription"
+
+cat >"$tempfile" <<EOF
+prescription:${prescription}
+insurance:$(sed -nr 's;^X-HEALTH-INSURANCE:(.*)\;.*\;\r?$;\1;p' "$cardfile")
+bday:$(sed -nr 's;^BDAY:(.*)\r?$;\1;p' "$cardfile")
+name:$(sed -rn '/^N[\;:]/{s;^N(\;[^:]*)?:([^\;]*)(\;[^\;]*)(\;[^\;]*)?(\;[^\;]*)?(\;[^\;]*)?\r?$;\5 \3 \4 \2 \6;;s;[\;,]; ;g;s; +; ;g;s;^ | $;;g;p}' "$cardfile")\n$(sed -nr 's;^ADR:(.*)\r?$;\1;p' "$cardfile")
+date:
+EOF
+
+echo -n "Location: ?p=prescriptions&edit=$prescription\n\n"
--- /dev/null
+/*
+# 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 <http://www.gnu.org/licenses/>.
+*/
+
+@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%;}
+
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+client="${_GET[client]}"
+edit="${_GET[edit]}"
+[ -n "$edit" ] && client="${edit%.*.mpx}.vcf"
+
+cat <<EOF
+
+<div id="${card}" class="card">$(view_card "$client")</div>
+
+<!--h1>$(l10n prescriptions_current)</h1-->
+
+<div class="newprescription">
+ <form action="?action=new_prescription" method="POST">
+ <input type="hidden" name="client" value="$client">
+ <button type="submit">$(l10n newprescription)</button>
+ </form>
+</div>
+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
+
+#<!--h1>$(l10n prescriptions_past)</h1-->
+
+# vi:set filetype=html:
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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"
+}
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+. $_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"
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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}"
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+check(){ [ "$1" = "$2" ] && printf checked}
+
+cat <<END_HTML
+<div class="prescription ${mpx[presctype]%_*} ${mpx[presctype]#*_}">
+<fieldset class="baseinfo">
+<label for="insurance">$(l10n insurance)</label>
+<span id="insurance">${mpx[insurance]}</span>
+<br>
+<label for="name">$(l10n name)</label>
+<label for="bday">$(l10n bday)</label>
+<span id="name">${mpx[name]}</span>
+<span id="bday">${mpx[bday]}</span>
+
+<br>
+<label for="date">$(l10n date)</label>
+<span id="date">${mpx[date]}</span>
+</fieldset>
+
+<fieldset class="misc">
+<h1 id="${mpx[prescription]}">$(l10n therapy_prescription)</h1>
+<br>
+<label for="addcontrib">$(l10n addcontrib)</label>
+<span id="addcontrib">${mpx[addcontrib]}</span>
+<label for="contribconfirm">$(l10n contribconfirm)</label>
+<span id="contribconfirm">${mpx[contribconfirm]}</span>
+<label class="checkbox ${mpx[contribreceipt]:+checked}" for="contribreceipt">$(l10n contribreceipt)</label>
+</fieldset>
+
+<label class="checkbox ${mpx[prescreviewed]:+checked}" for="prescreviewed">
+ $([ -n "${mpx[prescreviewed]}" ] && printf %s "$(l10n prescreviewed)" \
+ || printf %s "$(l10n prescreview)" )
+</label>
+
+<fieldset class="catalogue">
+<h2>$(l10n prescription_by_catalogue)</h2>
+<label class="radio $(check "$mpx[prescno]" first)" for="prescfirst">$(l10n prescfirst)</label>
+<br>
+<label class="radio $(check "$mpx[prescno]" follow1)" for="prescfollow1">$(l10n prescfollow1)</label>
+<br>
+<label class="radio $(check "$mpx[prescno]" follow2)" for="prescfollow2">$(l10n prescfollow2)</label>
+<br>
+<label class="radio $(check "$mpx[prescno]" other)" for="prescother">$(l10n prescother)</label>
+<br>
+<label class="radio $(check "$mpx[prescno]" continual)" for="presccontinual">$(l10n presccontinual)</label>
+
+<br>
+<label class="checkbox ${mpx[grouptherapy]:+checked}" for="grouptherapy">$(l10n grouptherapy)</label>
+<br>
+<label class="checkbox ${mpx[housecall]:+checked}" for="housecall">$(l10n housecall)</label>
+<br>
+<label class="checkbox ${mpx[report]:+checked}" for="report">$(l10n report)</label>
+</fieldset>
+
+<fieldset class="description">
+ <label for="quantity">$(l10n quantity)</label>
+ <label for="remidy">$(l10n remidy)</label>
+ <label for="quantity_weekly">$(l10n quantity_weekly)</label>
+ <span id="quantity">${mpx[quantity]}</span>
+ <span id="remidy">${mpx[remidy]}</span>
+ <span id="quantity_weekly">${mpx[quantity_weekly]}</span>
+$( for n in {0..10}; do
+ [ -n "${mpx[quantity$n]}" -o -n "${mpx[remidy$n]}" -o -n "${mpx[quantity_weekly$n]}" ] \
+ && printf '
+ <span id="quantity">%s</span>
+ <span id="remidy">%s</span>
+ <span id="quantity_weekly">%s</span>
+ ' "${mpx[quantity$n]}" "${mpx[remidy$n]}" "${mpx[quantity_weekly$n]}"
+done )
+<br>
+<p class="indicator_codes">
+ <label for="indicator">$(l10n indicator)</label>
+ <span id="indicator">${mpx[indicator]}</span>
+ <br>
+ <label for="icd10">$(l10n icd10)</label>
+ <span id="icd10">${mpx[icd10]}</span>
+</p>
+<br>
+<p class="indicator_reading">
+ <label for="indicator_reading">$(l10n indicator_reading)</label>
+ <span id="indicator_reading">${mpx[indicator_reading]}</span>
+</p>
+</fieldset>
+
+<p class="therapy_dates">
+ <label>$(l10n therapy_dates):</label>
+ $(therapy_dates "$id" \
+ | while read date; do
+ printf '<span>%s</span>' "$date"
+ done
+ )
+</p>
+
+<p class="issuer">
+ <label>$(l10n issuer)</label>
+ <span id="issuer">$mpx[issuer]</span>
+</p>
+
+<fieldset class="controls">
+<a class="button" href="?p=prescriptions&edit=${mpx[prescription]}#${mpx[prescription]}">$(l10n edit)</a>
+<a class="button" href="?p=therapy&id=${mpx[prescription]%.mpx}.tpy">$(l10n therapy)</a>
+</fieldset>
+
+</div>
+END_HTML
--- /dev/null
+#!/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
+}
--- /dev/null
+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;
+}
--- /dev/null
+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);
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+#. $_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
+}
--- /dev/null
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <<EOF
+[h1 $(l10n therapy)]
+
+[div .patient
+ [h2 . ⬅ $(VCF FN |HTML)]
+]
+
+[div .therapies
+ [h2 $(l10n therapies)]
+ $(
+ (cd "$_DATA/therapies/"; printf '%s\n' "${id%%.*}".*.tpy) \
+ | while read tpyfile; do
+ [ "$tpyfile" = "${id%%.*}.*.tpy" ] && break
+ tpy="${tpyfile%.tpy}";
+ tpydates="$(sed -En 's;^session[0-9]+_date:;;p;' "$_DATA/therapies/$tpyfile" \
+ | sort \
+ | sed -E ':X;N;$!bX; s;^[\n ]+;;; s;[\n ]+$;;; s;(\n.*\n|\n); - ;;'
+ )"
+ if [ "${tpy%.*}.${tpy#*.}" = "${id}" ]; then
+ printf '[a .item .therapy .current href="/therapies/%s" . %s] ' \
+ "${tpy%.*}/${tpy#*.}" "$(HTML "${tpydates:--}")"
+ else
+ printf '[a .item .therapy href="/therapies/%s" . %s] ' \
+ "${tpy%.*}/${tpy#*.}" "$(HTML "${tpydates:--}")"
+ fi
+ done |sort -n
+ )
+ [!-- a .item .therapy href="/therapies/${id%%.*}/new" . + --]
+]
+EOF
+
+if [ "$mpx" ]; then
+ printf '[div .prescription [h2 %s]' "$(l10n therapy_prescription)"
+
+ printf '[ul'
+ for n in '' 0 1 2 3 4 5 6 7 8 9 10; do
+ remidy="$(MPX remidy$n)"
+ quantity="$(MPX quantity$n)"
+ quantity_weekly="$(MPX quantity_weekly$n)"
+
+ if [ "$remidy" -a "$quantity_weekly" ]; then
+ printf '[li %s %s %s]' "$(HTML "$quantity")" "$(HTML "$remidy")" "$(HTML "$quantity_weekly") $(l10n weekly)"
+ elif [ "$remidy" ]; then
+ printf '[li %s %s]' "$(HTML "$quantity")" "$(HTML "$remidy")"
+ fi
+ done
+ printf ']'
+
+ for field in indicator icd10; do
+ val="$(MPX "$field")"
+ [ "$val" ] && printf '[span .%s [label . %s:]%s]' "$field" "$(l10n "$field")" "$(HTML "$val")"
+ done
+
+ if [ "$(MPX indicator_reading)" ]; then
+ printf '[h3 %s][p . %s]' \
+ "$(l10n indicator_reading)" "$(MPX indicator_reading |HTML)"
+ fi
+
+ printf ']'
+fi
+
+cat <<EOF
+[form #report method=POST action="/therapies/update_therapy.sh"
+ [hidden "id" "$id"][hidden "tid" "$(transid "$tpyfile")"]
+
+ [button #backbutton type=submit name="vcfreturn" value="true" ⬅ $(VCF FN |HTML)]
+
+ [input .stickynote type=checkbox name=c_stickynote #show_stickynote]
+ [fieldset .stickynote
+ [label for="show_stickynote" $(l10n notes)]
+ [h2 $(l10n notes)]
+ [textarea name=stickynote . $(TPY stickynote |HTML)]
+ [button type=submit $(l10n save)]
+ ]
+EOF
+
+# cat <<EOF
+# [input .stickynote type=checkbox name=c_timesheet #show_timesheet]
+# [fieldset .stickynote
+# [label for="show_timesheet" $(l10n timesheet)]
+# [h2 $(l10n timesheet)]
+# [table [thead [tr
+# [th $(l10n time_goal)][th $(l10n time_actual)][th $(l10n time_difference)]
+# ]][tbody
+# $(for n in '' 0 1 2 3 4 5 6 7 8 9 10; do
+# tsgoal="$(TPY tsgoal$n |grep -m1 -xE '[0-9]+')"
+# tsactual="$(TPY tsactual$n |grep -m1 -xE '[0-9]+')"
+# printf '[tr [td [input type=number name=tsgoal value="%s"]][td [input type=number name=tsactual value="%s"]][td %s]]' \
+# "$tsgoal" "$tsactual" "$(( ${tsgoal:-0} - ${tsactual:-0} ))"
+# done)
+# ]]
+# [button type=submit $(l10n save)]
+# ]
+# EOF
+
+cat <<EOF
+ [label .tab .heading
+ [span .no $(l10n number)][span .date $(l10n date)][span .therapist $(l10n therapist)][span .signature $(l10n signature)]
+ ]
+EOF
+
+therapy_sessions
+
+## ==== Drawing canvas ==== ##
+### ====================== ###
+
+penwidth="$(TPY penwidth |grep -xE -m1 '(4|12|36)' || printf '4')"
+color="$(TPY color |grep -xE -m1 '#(000|00A|0A0|0AA|A00|A0A|AA0|FFF)' || printf '#000')"
+cat <<EOF
+ [fieldset .penwidth
+ [radio "penwidth" "4" #pw1 $(checked "$penwidth" 4) ][label for="pw2"]
+ [radio "penwidth" "12" #pw2 $(checked "$penwidth" 12)][label for="pw3"]
+ [radio "penwidth" "36" #pw3 $(checked "$penwidth" 36)][label for="pw1"]
+ ]
+ [fieldset .color
+ [radio "color" "#000" .color #c000 $(checked "$color" '#000') ][label for=c000 ]
+ [radio "color" "#00A" .color #c001 $(checked "$color" '#00A') ][label for=c001 ]
+ [radio "color" "#0A0" .color #c010 $(checked "$color" '#0A0') ][label for=c010 ]
+ [radio "color" "#0AA" .color #c011 $(checked "$color" '#0AA') ][label for=c011 ]
+ [radio "color" "#A00" .color #c100 $(checked "$color" '#A00') ][label for=c100 ]
+ [radio "color" "#A0A" .color #c101 $(checked "$color" '#A0A') ][label for=c101 ]
+ [radio "color" "#AA0" .color #c110 $(checked "$color" '#AA0') ][label for=c110 ]
+ [radio "color" "#FFF" .color #c111 $(checked "$color" '#FFF') ][label for=c111 ]
+ ]
+ [img .dotmark .bg src="/therapies/therapy_background.png" alt="WARNING: Missing background image!"]
+ [canvas #canvas .dotmark .ov width="${bg_dim%x*}" height="${bg_dim#*x}" ]
+ [input type=hidden #image_serialize name=imagedata value=""]
+
+ [input type=hidden name=formend value=formend]
+ [button #savebutton type=submit $(l10n save)]
+]
+
+[span #jsdebug style="display: none; position: fixed; right:0; bottom:0" Debug]
+
+[script type="text/javascript" src="/therapies/therapy_draw.js"]
+[!-- script type="text/javascript" src="/therapies/autosave.js" --]
+EOF
--- /dev/null
+* { position: static; }
+
+.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; }
+
+.trailbtn:checked + fieldset.trailbox { display: block;}
+
+.trailbtn:checked + .trailbox + .trailbtn {
+ display: block;
+ height: 2.25em; padding: 0 3ex;
+ font-size: 1em; font-weight: normal;
+ color: #000; background-color: #FDD;
+ border: 1px solid #000;
+ border-radius: 4px;
+}
+.trailbtn:checked + .trailbox + .trailbtn[type=submit]:before {content: none;}
+
+body {
+ overflow: scroll;
+ position: relative;
+ width: 100%;
+ margin: 0; padding: 0;
+ padding-top: 2em;
+}
+
+form > button[type=submit] {
+ position: fixed; display: block;
+ top: 0; right: 2.5em;
+ height: 2.25em; padding: 0 3ex;
+ font-size: 1em; font-weight: bold;
+ color: #000; background-color: #FDD;
+ border-width: 1px; border-color: #000;
+ border-style: none solid solid solid;
+ border-radius: 0 0 4px 4px;
+ z-index: 3;
+}
+form > button[type=submit]:hover {
+ background-color: #FEE;
+}
+
+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;}
--- /dev/null
+// 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 <http://www.gnu.org/licenses/>.
+
+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) } )
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+. "$_EXEC/pdiread.sh"
+tpy="$(POST id)"
+
+tpyfile="$_DATA/therapies/${tpy}.tpy"
+tempfile="$_DATA/temp/${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
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+
+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"