diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be85e95 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/sonarlint diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is 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. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - 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. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - 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 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. Use with the GNU Affero General Public License. - - 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 Affero 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 special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 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 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 General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - 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 GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/backend/class/App.php b/backend/class/App.php new file mode 100644 index 0000000..de35b73 --- /dev/null +++ b/backend/class/App.php @@ -0,0 +1,171 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.15 + * @link https://nox.kiwi/ + */ +abstract class App extends Singleton implements AppInterface +{ + use LogTrait; + use LanguageImprovementTrait; + + /** @var string I am the called App's vendor. */ + private static string $vendor; + /** @var string I am the called App's name. */ + private static string $app; + + /** + * I will initialize the App class. + * This step consists of loading an environment and loading an Application instance. + * Also, I will check the maintenance status. + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + protected function initialize(): void + { + parent::initialize(); + $namespaceName = (new ReflectionClass($this))->getNamespaceName(); + self::$app = explode('\\', $namespaceName)[1]; + self::$vendor = explode('\\', $namespaceName)[0]; + $this->checkMaintenance(); + Hook::run('APP_INITIALIZING'); + try { + Environment::getInstance(); + Application::getInstance(); + } catch (\Exception $exception) { + MaintenanceGate::getInstance()->close($exception->getCode()); + ErrorHandler::handleException(new ConfigurationException('APP_INIT_FAIL', E_ERROR, $exception)); + } + Hook::run('APP_INITIALIZED'); + } + + /** + * I will check the maintenance mode and stop the app if needed. + */ + final protected function checkMaintenance(): void + { + try { + if (MaintenanceGate::getInstance()->isOpen()) { + return; + } + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + } + Hook::run('APP_CONSTRUCT_MAINTENANCEMODE'); + FrontendHelper::outputExit(MaintenanceGate::MAINTENANCE_TEMPLATE, HttpResponse::HEADER_ERROR, WebHelper::HTTP_SERVER_ERROR); + } + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + #[NoReturn] public function run(): void + { + $request = Request::getInstance(); + Hook::run('APP_RUN_START'); + $request->set(Mvc::TEMPLATE, self::getTemplate()); + $this->logDebug(__METHOD__, $request->get()); + $contextName = static::getVendor(); + $contextName .= "\\{$this->returnIt(static::getApp())}"; + $contextName .= "\\Context"; + $contextName .= '\\' . ucfirst($request->get(Mvc::CONTEXT, '')) . 'Context'; + try { + $contextInstance = Context::get($contextName); + if (! $contextInstance->isAllowed()) { + Hook::run('APP_RUN_FORBIDDEN'); + LinkHelper::forward([Mvc::CONTEXT => 'login', Mvc::VIEW => 'login']); + } + Hook::run('APP_RUN_ALLOWED'); + $contextInstance->dispatch($request); + Hook::run('APP_RUN_END'); + } catch (\Exception $exception) { + MaintenanceGate::getInstance()->close($exception->getCode()); + ErrorHandler::handleException($exception); + exit(WebHelper::HTTP_SERVER_ERROR); + } + exit(WebHelper::HTTP_OKAY); + } + + /** + * I return the vendor of this App + * + * @return string + */ + final public static function getVendor(): string + { + if (empty(self::$vendor)) { + return ''; + } + + return self::$vendor; + } + + /** + * Simply returns the App's name + * + * @return string + */ + final public static function getApp(): string + { + if (empty(self::$app)) { + return ''; + } + + return self::$app; + } + + /** + * I will return the template that will be used for displaying the page. + * It works by checking: + * -> Has a template been requested through the Request? + * -> Has the called $context/$view been configured to use a template? + * -> Has the called $app been configured to use a template? + * -> Otherwise return 'blank' template. + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return string + */ + private static function getTemplate(): string + { + $request = Request::getInstance(); + $template = $request->get(Mvc::TEMPLATE); + if (is_string($template)) { + return $template; + } + $context = $request->get(Mvc::CONTEXT); + $view = $request->get(Mvc::VIEW); + $template = (string)Application::getInstance()->get("context>$context>view>$view>template", ''); + if (! empty($template)) { + return $template; + } + $template = (string)Application::getInstance()->get("context>$context>template", ''); + if (! empty($template)) { + return $template; + } + + return Application::getInstance()->get('defaulttemplate', 'blank'); + } +} diff --git a/backend/class/Application.php b/backend/class/Application.php new file mode 100644 index 0000000..176c258 --- /dev/null +++ b/backend/class/Application.php @@ -0,0 +1,135 @@ + + * @license https://nox.kiwi/license + * @copyright 2019 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class Application extends Singleton +{ + use DatacontainerTrait; + + private const CONFIG_DEFAULT_VIEW = 'defaultview'; + + /** + * I will construct the Application object. + * @throws \noxkiwi\core\Exception + * @throws \noxkiwi\core\Exception\ConfigurationException + * @throws \noxkiwi\core\Exception\InvalidArgumentException + */ + protected function __construct() + { + parent::__construct(); + $this->init(); + } + + /** + * I will initialize the Application class. + * @throws \noxkiwi\core\Exception + * @throws \noxkiwi\core\Exception\ConfigurationException + * @throws \noxkiwi\core\Exception\InvalidArgumentException + */ + private function init(): void + { + $cachedConfig = Cache::getInstance()->get(Cache::DEFAULT_PREFIX, '_CONFIG_APP'); + if (! empty($cachedConfig) && is_array($cachedConfig)) { + $this->add($cachedConfig); + + return; + } + try { + $appConfig = (new JsonConfig(Path::CONFIG_APPLICATION))->get(); + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + throw new ConfigurationException('app.json is invalid JSON', E_ERROR, 'INVALID_JSON'); + } + if (! isset($appConfig[Mvc::CONTEXT])) { + throw new ConfigurationException('EXCEPTION_GETCONFIG_APPCONFIGCONTAINSNOCONTEXT', E_ERROR, $appConfig); + } + $default = (new JsonConfig(Path::getHomeDir() . Path::CONFIG_APPLICATION, true))->get(); + $appConfig = ArrayHelper::arrayMergeRecursive($appConfig, $default); + foreach ($appConfig[Mvc::CONTEXT] as $contextName => $contextData) { + if (! isset($contextData['type'])) { + continue; + } + try { + $contextType = (new JsonConfig(Path::CONFIG_CONTEXT_DIR . '/' . $appConfig[Mvc::CONTEXT][$contextName]['type'] . '.json'))->get(); + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + throw new ConfigurationException('EXCEPTION_GETCONFIG_APPCONFIGCONTEXTTYPEINVALID', E_ERROR, 'INVALID_JSON'); + } + $errors = ContextValidator::getInstance()->validate($contextType); + if (! empty($errors)) { + throw new ConfigurationException('EXCEPTION_GETCONFIG_APPCONFIGCONTEXTTYPEINVALID', E_ERROR, $errors); + } + $appConfig[Mvc::CONTEXT][$contextName] = ArrayHelper::arrayMergeRecursive($appConfig[Mvc::CONTEXT][$contextName], $contextType); + if (is_array($appConfig[Mvc::CONTEXT][$contextName][self::CONFIG_DEFAULT_VIEW]) > 1) { + $appConfig[Mvc::CONTEXT][$contextName][self::CONFIG_DEFAULT_VIEW] = $appConfig[Mvc::CONTEXT][$contextName][self::CONFIG_DEFAULT_VIEW][0]; + } + } + $errors = AppValidator::getInstance()->validate($appConfig); + if (! empty($errors)) { + throw new ConfigurationException('EXCEPTION_GETCONFIG_APPCONFIGFILEINVALID', E_ERROR, $errors); + } + Cache::getInstance()->set(Cache::DEFAULT_PREFIX, '_CONFIG_APP', $appConfig); + $this->add($appConfig); + } + + /** + * I will return true if the given $Context exists in the current App. + * + * @param string|null $context + * + * @return bool + */ + public function contextExists(?string $context = null): bool + { + $context ??= Request::getInstance()->get(Mvc::CONTEXT); + try { + return self::getInstance()->exists("context>$context"); + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + } + + return false; + } + + /** + * I will return true if the $view exists in the $Context of the current App. + * + * @param string|null $context + * @param string|null $view + * + * @return bool + */ + public function viewExists(?string $context = null, ?string $view = null): bool + { + $context ??= Request::getInstance()->get(Mvc::CONTEXT); + $view ??= Request::getInstance()->get(Mvc::VIEW); + try { + return $this->exists("context>$context>view>$view"); + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + } + + return false; + } +} diff --git a/backend/class/Auth.php b/backend/class/Auth.php new file mode 100644 index 0000000..57f6003 --- /dev/null +++ b/backend/class/Auth.php @@ -0,0 +1,20 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.2 + * @link https://nox.kiwi/ + */ +abstract class Auth extends Singleton implements AuthInterface +{ + protected const USE_DRIVER = true; +} diff --git a/backend/class/Config.php b/backend/class/Config.php new file mode 100644 index 0000000..2d52833 --- /dev/null +++ b/backend/class/Config.php @@ -0,0 +1,19 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +class Config +{ + use DatacontainerTrait; +} diff --git a/backend/class/Config/JsonConfig.php b/backend/class/Config/JsonConfig.php new file mode 100644 index 0000000..4ac745b --- /dev/null +++ b/backend/class/Config/JsonConfig.php @@ -0,0 +1,101 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.1 + * @link https://nox.kiwi/ + */ +final class JsonConfig extends Config +{ + /** @var string I am the file name */ + private string $file; + + /** + * Creates a config instance and loads the given JSON configuration file as content + * + * @param string $file + * @param bool $inherit Combine all parent App's configuration with the local one. + * + * @throws \noxkiwi\core\Exception\ConfigurationException + * @throws \noxkiwi\core\Exception\InvalidArgumentException The file parameter is an empty string. + * @throws \noxkiwi\singleton\Exception\SingletonException Something really awful happened. + */ + public function __construct(string $file, bool $inherit = false) + { + if (empty($file)) { + throw new InvalidArgumentException('FILE_IS_EMPTY', E_ERROR); + } + $config = []; + if (! $inherit) { + $this->file = (string)$this->getFullPath($file); + $config = $this->decodeFile($this->file); + if (! is_array($config)) { + throw new ConfigurationException('CONTENT_IS_NOT_AN_ARRAY', E_ERROR, compact('file', 'config')); + } + $this->put($config); + } + $fullPath = Path::getHomeDir() . $file; + if (! Filesystem::getInstance()->fileAvailable($fullPath)) { + return; + } + $thisConf = $this->decodeFile($fullPath); + $config = array_replace_recursive($config, $thisConf); + parent::__construct($config); + } + + /** + * I will give you the lowest level full path that exists in the appstack. + * + * @param string $file + * + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return string|null + */ + private function getFullPath(string $file): ?string + { + if (Filesystem::getInstance()->fileAvailable($file)) { + return $file; + } + $fullPath = Path::getHomeDir() . $file; + if (Filesystem::getInstance()->fileAvailable($file)) { + return $fullPath; + } + + return Path::getInheritedPath($file); + } + + /** + * I will decode the given file and return the array of configuration it holds. + * + * @param string $fullPath + * + * @return array|null + */ + protected function decodeFile(string $fullPath): ?array + { + try { + return JsonHelper::decodeFileToArray($fullPath); + } catch (InvalidJsonException $exception) { + ErrorHandler::handleException($exception); + + return []; + } + } +} diff --git a/backend/class/Constants/Bootstrap.php b/backend/class/Constants/Bootstrap.php new file mode 100644 index 0000000..24f370d --- /dev/null +++ b/backend/class/Constants/Bootstrap.php @@ -0,0 +1,90 @@ + + * + * @copyright 2019 noxkiwi + * @version 1.0.0 + */ +abstract class Bootstrap +{ + public const CONTROL_LABEL = 'control-label'; + public const COL_LG_3 = 'col-lg-3'; + public const TEXT_DEF = 'text-default'; + public const TEXT_PRI = 'text-primary'; + public const TEXT_SEC = 'text-secondary'; + public const TXT_MUTED = 'text-secondary'; + public const TEXT_SUC = 'text-success'; + public const TXT_INFO = 'text-info'; + public const TEXT_WRN = 'text-warning'; + public const TEXT_DNG = 'text-danger'; + public const TEXT_DRK = 'text-dark'; + public const TXT_COLORS = [ + self::TEXT_DEF, + self::TEXT_PRI, + self::TEXT_SEC, + self::TXT_MUTED, + self::TEXT_SUC, + self::TXT_INFO, + self::TEXT_WRN, + self::TEXT_DNG, + self::TEXT_DRK + ]; + public const BTN = 'btn'; + public const BTN_DEFAULT = 'btn-default'; + public const BTN_PRIMARY = 'btn-primary'; + public const BTN_SECONDARY = 'btn-secondary'; + public const BTN_SUCCESS = 'btn-success'; + public const BTN_INFO = 'btn-info'; + public const BTN_WARNING = 'btn-warning'; + public const BTN_DANGER = 'btn-danger'; + public const BTN_DARK = 'btn-dark'; + public const BTN_LIGHT = 'btn-light'; + public const BTN_LINK = 'btn-link'; + public const BTN_OUTLINE_PRIMARY = 'btn-outline-primary'; + public const BTN_OUTLINE_SECONDARY = 'btn-outline-secondary'; + public const BTN_OUTLINE_SUCCESS = 'btn-outline-success'; + public const BTN_OUTLINE_INFO = 'btn-outline-info'; + public const BTN_OUTLINE_WARNING = 'btn-outline-warning'; + public const BTN_OUTLINE_DANGER = 'btn-outline-danger'; + public const BTN_OUTLINE_DARK = 'btn-outline-dark'; + public const BTN_OUTLINE_LIGHT = 'btn-outline-light'; + public const BTN_OUTLINE_LINK = 'btn-outline-link'; + public const BTN_STYLES = [ + self::BTN_PRIMARY, + self::BTN_SECONDARY, + self::BTN_SUCCESS, + self::BTN_INFO, + self::BTN_WARNING, + self::BTN_DANGER, + self::BTN_DARK, + self::BTN_LIGHT, + self::BTN_LINK, + self::BTN_OUTLINE_PRIMARY, + self::BTN_OUTLINE_SECONDARY, + self::BTN_OUTLINE_SUCCESS, + self::BTN_OUTLINE_INFO, + self::BTN_OUTLINE_WARNING, + self::BTN_OUTLINE_DANGER, + self::BTN_OUTLINE_DARK, + self::BTN_OUTLINE_LIGHT, + self::BTN_OUTLINE_LINK + ]; + public const BTN_LARGE = 'btn-lg'; + public const BTN_SMALL = 'btn-sm'; + public const BTN_BLOCK = 'btn-block'; + public const BTN_NORMAL = ''; + public const BTN_SIZES = [ + self::BTN_LARGE, + self::BTN_SMALL, + self::BTN_NORMAL, + self::BTN_BLOCK + ]; + public const FORM_GROUP = 'form-group'; + public const BTN_GROUP = 'btn-group'; +} diff --git a/backend/class/Constants/Mvc.php b/backend/class/Constants/Mvc.php new file mode 100644 index 0000000..7764f79 --- /dev/null +++ b/backend/class/Constants/Mvc.php @@ -0,0 +1,26 @@ + + * @license https://nox.kiwi/license + * @copyright 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class Mvc +{ + public const CONTEXT = 'context'; + public const VIEW = 'view'; + public const ACTION = 'action'; + public const TEMPLATE = 'template'; + public const ALL = [ + self::CONTEXT, + self::VIEW, + self::ACTION, + self::TEMPLATE + ]; +} diff --git a/backend/class/Constants/Validator.php b/backend/class/Constants/Validator.php new file mode 100644 index 0000000..b7ce36c --- /dev/null +++ b/backend/class/Constants/Validator.php @@ -0,0 +1,24 @@ + + * @license https://nox.kiwi/license + * @copyright 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class Validator +{ + public const TEXT = 'text'; + public const NUMBER = 'number'; + public const ANY = ''; + public const ALL = [ + self::TEXT, + self::NUMBER, + self::ANY + ]; +} diff --git a/backend/class/Context.php b/backend/class/Context.php new file mode 100644 index 0000000..58bf0ea --- /dev/null +++ b/backend/class/Context.php @@ -0,0 +1,227 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class Context extends Singleton implements ContextInterface +{ + use LanguageImprovementTrait; + use LogTrait; + use TranslatorTrait; + + protected const LOG_LEVELS = [ + LogLevel::EMERGENCY, + LogLevel::ALERT, + LogLevel::CRITICAL, + LogLevel::ERROR, + LogLevel::WARNING + ]; + /** @var \noxkiwi\core\Request I am the Request that is being processed by the Context. */ + protected Request $request; + /** @var \noxkiwi\core\Response I am the Response that the Context creates upon the Response. */ + protected Response $response; + /** @var \noxkiwi\core\Session I am the Session that is used for the Context. */ + protected Session $session; + + /** + * Context constructor. + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + protected function __construct() + { + $this->addLogger( + static::LOG_LEVELS, + Log::getInstance() + ); + $this->request = Request::getInstance(); + $this->response = Response::getInstance(); + $this->session = Session::getInstance(); + parent::__construct(); + $this->logDebug(static::class . ' created'); + if (extension_loaded('newrelic')) { + newrelic_name_transaction(static::class . '_' . $this->request->get(Mvc::VIEW)); + } + } + + /** + * I will return the given $contextName's Context object. + * + * @param string $contextName + * + * @throws \noxkiwi\core\Exception\SystemComponentException The desired context is not available. + * @return \noxkiwi\core\Context + */ + public static function get(string $contextName): Context + { + if (! class_exists($contextName)) { + throw new SystemComponentException('CONTEXT_NOT_AVAILABLE', E_ERROR, $contextName); + } + + return $contextName::getInstance(); + } + + /** + * I will make sure that the client is allowed to take these actions. + * + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return bool + */ + public function isAllowed(): bool + { + return Session::getInstance()->identify(); + } + + /** + * @inheritDoc + * @throws \noxkiwi\core\Exception + */ + public function dispatch(Request $request): void + { + try { + $response = $this->backendController($request); + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + throw new ContextException('Dispatching failed while running Backend Controller method.', E_ERROR, $exception); + } + try { + $this->frontendController($response); + } catch (\Exception $exception) { + ErrorHandler::handleException($exception); + throw new ContextException('Dispatching failed while running Frontend Controller method.', E_ERROR, $exception); + } + } + + /** + * I will perform any actions that belong to the backend of the application. + * + * @param \noxkiwi\core\Request $request + * + * @return \noxkiwi\core\Response + */ + final protected function backendController(Request $request): Response + { + $this->doAction($request); + $this->doView($request); + + return $this->response; + } + + /** + * I will perform the action if it is required. + * + * @param \noxkiwi\core\Request $request + */ + final protected function doAction(Request $request): void + { + $action = $request->get('action'); + if ($action === null) { + return; + } + $action = static::makeActionMethod($action); + if (! method_exists($this, $action)) { + return; + } + $this->$action(); + } + + /** + * I will execute the desired view function on the context if it is found. + * + * @param \noxkiwi\core\Request $request + */ + final protected function doView(Request $request): void + { + $method = static::getViewName($request->get(Mvc::VIEW, '')); + if (method_exists($this, $method)) { + $this->{$method}(); + } + } + + /** + * I will solely return the given $view's method name. + * + * @param string $view + * + * @return string + */ + #[Pure] final protected static function getViewName(string $view): string + { + return Mvc::VIEW . ucfirst($view); + } + + /** + * I will use the results of the frontendController to produce a valid response for the result data. + * + * @param \noxkiwi\core\Response $response + */ + protected function frontendController(Response $response): void + { + $this->doShow($response); + $this->doOutput($response); + } + + /** + * I will parse the view's front-end file before processing the template's frontend file. + * + * @param \noxkiwi\core\Response $response + */ + protected function doShow(Response $response): void + { + // View + $viewFile = "{$this->returnIt(Path::VIEW_DIR)}/{$this->returnIt($response->get(Mvc::CONTEXT))}/{$this->returnIt($response->get(Mvc::VIEW))}.php"; + $viewPath = Path::getInheritedPath($viewFile); + $viewContent = FrontendHelper::parseFile($viewPath, $response); + $response->set('content', $viewContent); + // Template + $templateFile = Path::TEMPLATE_DIR . '/' . Request::getInstance()->get(Mvc::TEMPLATE) . '/' . Path::TEMPLATE_FILE; + $templatePath = Path::getInheritedPath($templateFile); + $templateContent = FrontendHelper::parseFile($templatePath, $response); + $response->setOutput($templateContent); + } + + /** + * I will generate the finalizing output of the given $response. + * + * @param \noxkiwi\core\Response $response + */ + final protected function doOutput(Response $response): void + { + $response->pushOutput(); + } + + /** + * I will solely return the given $action's method name. + * + * @param string $action + * + * @return string + */ + #[Pure] private static function makeActionMethod(string $action): string + { + return 'action' . ucfirst($action); + } +} diff --git a/backend/class/Context/AllowallContext.php b/backend/class/Context/AllowallContext.php new file mode 100644 index 0000000..2dcee99 --- /dev/null +++ b/backend/class/Context/AllowallContext.php @@ -0,0 +1,27 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class AllowallContext extends Context +{ + /** + * @inheritDoc + */ + public function isAllowed(): bool + { + parent::isAllowed(); + + return true; + } +} diff --git a/backend/class/Context/ResourceContext.php b/backend/class/Context/ResourceContext.php new file mode 100644 index 0000000..4cefd14 --- /dev/null +++ b/backend/class/Context/ResourceContext.php @@ -0,0 +1,38 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class ResourceContext extends Context +{ + /** + * I will output the content of the requested file, and then I will exit. + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + #[NoReturn] protected function viewFile(): void + { + [$type, $file] = explode( + '/', + $this->request->get('file', '') + ); + [$folder, $extension] = MimeHelper::getResourceFromType($type); + MimeHelper::sendHeaders($type); + echo Filesystem::getInstance()->fileRead(Path::getInheritedPath("resource/$folder/$file.$extension")); + exit(WebHelper::HTTP_OKAY); + } +} diff --git a/backend/class/Cookie.php b/backend/class/Cookie.php new file mode 100644 index 0000000..3cbe62a --- /dev/null +++ b/backend/class/Cookie.php @@ -0,0 +1,47 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.2 + * @link https://nox.kiwi/ + */ +abstract class Cookie extends Singleton implements CookieInterface +{ + protected const USE_DRIVER = true; + + /** + * I will create a Session ID for the Session + * @return string + */ + final protected function makeSessionid(): string + { + return hash('sha512', uniqid((string)time()) . time()); + } + + /** + * I will return the expiration of the Cookie. + * @return int + */ + final protected function getExpires(): int + { + return time() + 3600; + } + + /** + * I will return the Cookie's domain. + * @return string + */ + final protected function getDomain(): string + { + return '/'; + } +} diff --git a/backend/class/Cookie/CookieCookie.php b/backend/class/Cookie/CookieCookie.php new file mode 100644 index 0000000..a6010fb --- /dev/null +++ b/backend/class/Cookie/CookieCookie.php @@ -0,0 +1,114 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CookieCookie extends Cookie +{ + /** + * @inheritDoc + * @throws \noxkiwi\core\Exception\CookieException + */ + public function start(): Cookie + { + if (! $this->exists(Session::SESSIONKEY)) { + $this->set(Session::SESSIONKEY, $this->makeSessionid()); + } + if ($this->get(Session::SESSIONKEY) === null) { + throw new CookieException('EXCEPTION_COOKIES_NOT_WRITABLE', E_NOTICE); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function end(): void + { + foreach ($this->get() as $key => $value) { + $this->remove($key); + } + } + + /** + * @inheritDoc + * + * @throws \noxkiwi\core\Exception\CookieException + */ + public function add(array $data): void + { + foreach ($data as $key => $value) { + $this->set($key, $value); + } + } + + /** + * @inheritDoc + */ + public function get(string $key = null, mixed $default = null): mixed + { + if (empty($key)) { + return $_COOKIE; + } + + return $_COOKIE[$key] ?? $default; + } + + /** + * @inheritDoc + * + * @throws \noxkiwi\core\Exception\CookieException + */ + public function set(string $key, mixed $data): void + { + if ($data === null) { + $this->remove($key); + + return; + } + setcookie($key, $data, $this->getExpires(), '/', $_SERVER['HTTP_HOST'], false, true); + $_COOKIE[$key] = $data; + if ($this->get($key) === null) { + throw new CookieException('EXCEPTION_COOKIES_NOT_WRITABLE', E_NOTICE); + } + } + + /** + * @inheritDoc + */ + public function remove(string $key): void + { + unset($_COOKIE[$key]); + setcookie($key, '', -1, '/'); + } + + /** + * @inheritDoc + */ + public function exists(string $key): bool + { + return isset($_COOKIE[$key]); + } + + /** + * @inheritDoc + */ + public function put(array $data): void + { + + } +} diff --git a/backend/class/Datacontainer.php b/backend/class/Datacontainer.php new file mode 100644 index 0000000..5d64a16 --- /dev/null +++ b/backend/class/Datacontainer.php @@ -0,0 +1,22 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +class Datacontainer implements DatacontainerInterface +{ + use DatacontainerTrait; +} diff --git a/backend/class/Environment.php b/backend/class/Environment.php new file mode 100644 index 0000000..e2cc196 --- /dev/null +++ b/backend/class/Environment.php @@ -0,0 +1,146 @@ + + * @license https://nox.kiwi/license + * @copyright 2019 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class Environment extends Singleton implements DatacontainerInterface +{ + use DatacontainerTrait; + use LanguageImprovementTrait; + + public const PRODUCTION = 'production'; + public const UAT = 'uat'; + public const BETA = 'beta'; + public const ALPHA = 'alpha'; + public const DEVELOPMENT = 'development'; + public const STAGES = [ + self::PRODUCTION, + self::UAT, + self::BETA, + self::ALPHA, + self::DEVELOPMENT + ]; + public static bool $loaded; + + /** + * Environment constructor. + * + * + * @throws \noxkiwi\core\Exception + * @throws \noxkiwi\core\Exception\ConfigurationException + * @throws \noxkiwi\core\Exception\InvalidJsonException + */ + protected function initialize(): void + { + parent::initialize(); + $data = JsonHelper::decodeFileToArray(self::getPath()); + if (empty($data)) { + throw new ConfigurationException('ENVIRONMENT_INVALID', E_ERROR); + } + $this->add($data); + self::$loaded = true; + } + + /** + * I will return the path to the environment config file. + * @throws \noxkiwi\core\Exception\ConfigurationException + * @return string + */ + private static function getPath(): string + { + if (! defined('NK_ENVIRONMENT')) { + throw new ConfigurationException('NO ENVIRONMENT CONSTANT "MK_ENVIRONMENT" DEFINED', E_ERROR); + } + + return NK_ENVIRONMENT; + } + + /** + * Returns the current environment identifier + * + * @return string + */ + public static function getCurrent(): string + { + if (defined('CORE_ENVIRONMENT')) { + return CORE_ENVIRONMENT; + } + define('CORE_ENVIRONMENT', self::PRODUCTION); + + return CORE_ENVIRONMENT; + } + + /** + * I will return the application's configuration tree identified by the given $type and $key + * + * @example get(Mvc::CONTEXT, 'home') // APPJSON[Mvc::CONTEXT]['home'] + * + * @param string $type + * @param string $identifier + * + * @return mixed + * @throws \noxkiwi\core\Exception\ConfigurationException + */ + public function getDriverConfig(string $type, string $identifier): mixed + { + $value = $this->get("$type>$identifier"); + if (! empty($value)) { + return $value; + } + if (! $this->exists($type)) { + throw new ConfigurationException("Environment: Type "$type" is not configured.", E_ERROR, func_get_args()); + } + throw new ConfigurationException("Environment: Entry "$identifier" in Type "$type" is not configured.", E_ERROR, func_get_args()); + } + + /** + * I will return whether the given $environment really is currenty being used or not. + * + * You can either put a single Environment constant here, or an array of Environment constants. + * + * @example Environment::runs(Environment::PRODUCITON); + * @example Environment::runs([Environment::UAT, Environment::PRODUCTION]); + * + * @param array|string $environment + * + * @return bool + */ + public static function runs(array|string $environment): bool + { + if (is_array($environment)) { + foreach ($environment as $item) { + if (self::runs($item)) { + return true; + } + } + + return false; + } + if (! in_array($environment, self::STAGES, true)) { + return false; + } + + return self::getCurrent() === $environment; + } +} diff --git a/backend/class/Error.php b/backend/class/Error.php new file mode 100644 index 0000000..6459197 --- /dev/null +++ b/backend/class/Error.php @@ -0,0 +1,53 @@ +detail = []; + } + + /** + * I will return the detail array. + * @return array + */ + final public function getDetail(): array + { + return $this->detail; + } + + /** + * I will set the detail to the given array. + * + * @param array|null $detail + * + * @return \noxkiwi\core\Error + */ + final public function setDetail(array $detail = null): Error + { + $this->detail = $detail ?? []; + + return $this; + } +} diff --git a/backend/class/ErrorHandler.php b/backend/class/ErrorHandler.php new file mode 100644 index 0000000..0f64b64 --- /dev/null +++ b/backend/class/ErrorHandler.php @@ -0,0 +1,200 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.1 + * @link https://nox.kiwi/ + */ +abstract class ErrorHandler +{ + private const SURROUND_LINES = 10; + + /** + * I will handle the given $error as info element on a new Exception that will be handled right after. + * + * @param \Error $error + * @param int $errorLevel + */ + final public static function handleError(\Error $error, int $errorLevel = E_ERROR): void + { + try { + throw new PhpException('ERROR_OCCURED', $errorLevel, $error->getMessage()); + } catch (Exception $exception) { + static::handleException($exception, $errorLevel); + } + } + + /** + * I will handle the given $exception and perform some actions with it. + * In every case, the output of the handled Exception will be generated as a file. + * In case the $errorLevel is E_ERROR and you are running on PRODUCTION environment, + * the Maintenance gate will close with the Exception's reason. So be aware that + * you WILL have to handle all exceptions that are not CRITICAL or DESTRUCTIVE for + * your product as expected or give them a lower error level. + * + * @param \Exception $exception + * @param int $errorLevel + */ + public static function handleException(\Exception $exception, int $errorLevel = E_ERROR): void + { + if (error_reporting() === 0) { + return; + } + try { + static::output($exception); + if (! $exception instanceof Exception) { + return; + } + if ($errorLevel === E_ERROR && Environment::runs(Environment::PRODUCTION)) { + MaintenanceGate::getInstance()->close($exception->getMessage()); + } + } catch (\Exception $loggerException) { + var_dump($exception); + var_dump($loggerException); + } + die(WebHelper::HTTP_SERVER_ERROR); + } + + /** + * I will output the given $exception data if the environment is correct. + * In any case, I will create a log of the handled Exception + * + * @see \noxkiwi\core\Path::LOG_DIR + * + * @param \Exception $exception + */ + private static function output(\Exception $exception): void + { + if (error_reporting() === 0) { + return; + } + $file = basename($exception->getFile()); + $file = Path::LOG_DIR . "exception_{$file}_{$exception->getLine()}.html"; + $errorPage = FrontendHelper::parseFile(Path::getInheritedPath('frontend/page/errorinfo.php'), $exception); + file_put_contents($file, $errorPage); + if (defined('NK_ERROR_OUTPUT') && NK_ERROR_OUTPUT !== true) { + return; + } + if (! headers_sent()) { + header(HttpResponse::HEADER_ERROR); + } + if (Environment::runs(Environment::PRODUCTION)) { + echo FrontendHelper::parseFile(Path::getInheritedPath('frontend/page/error.php')); + exit(CliResponse::EXITCODE_ERROR); + } + echo $errorPage; + exit(CliResponse::EXITCODE_ERROR); + } + + /** + * I will build the output of the given $exception's trace. + * This includes code, so if the code is "secret" for any reason, + * disable the stacktrace output through your Environment. + * + * @param \Exception $exception + * + * @return string + */ + final public static function getStack(\Exception $exception): string + { + $stackTrace = ''; + foreach ($exception->getTrace() as $trace) { + $surround = static::getSurround($trace['file'] ?? '', $trace['line'] ?? 0); + $uniqid = uniqid('stackTrace', false); + $arguments = print_r($trace['args'] ?? null, true); + $trace['class'] ??= 'none'; + $stackTrace .= << +

+ +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
File{$trace['file']}
Line{$trace['line']}
Class{$trace['class']}
Function{$trace['function']}
Args
$arguments
Surrounding
$surround
+
+
+ +HTML; + } + + return $stackTrace; + } + + /** + * I will solely return a fixed amount of lines from the given $file. + * I will take care that [n] lines are shown above and below the given $errorLine. + * + * @see \noxkiwi\core\ErrorHandler::SURROUND_LINES + * + * @param string $file + * @param int $errorLine + * + * @return string + */ + final public static function getSurround(string $file, int $errorLine): string + { + if (empty($file) || empty($errorLine)) { + return ''; + } + $ret = ''; + $sourceLines = explode(chr(10), file_get_contents($file)); + $startLine = max(0, $errorLine - static::SURROUND_LINES); + $endLine = min(count($sourceLines), $errorLine + static::SURROUND_LINES); + for ($lineNumber = $startLine; $lineNumber <= $endLine; $lineNumber++) { + $sLine = $lineNumber + 1; + $a = '⚫ '; + if ($sLine === $errorLine) { + $a = '🔴 '; + } + $ret .= $sLine . $a . ($sourceLines[$lineNumber] ?? '') . chr(10); + } + + return htmlspecialchars($ret); + } +} diff --git a/backend/class/ErrorStack.php b/backend/class/ErrorStack.php new file mode 100644 index 0000000..e173632 --- /dev/null +++ b/backend/class/ErrorStack.php @@ -0,0 +1,142 @@ +attach(new ErrorstackObserver()); + $this->notify(ErrorstackObserver::NOTIFY_ADDINSTANCE); + $this->name = $name; + } + + /** + * Returns an ErrorStack instance identified by the given $type + * + * @param string $type + * + * @return \noxkiwi\core\ErrorStack + */ + public static function getErrorStack(string $type): ErrorStack + { + if (! isset(self::$errorStacks[$type])) { + self::$errorStacks[$type] = new Errorstack($type); + } + + return self::$errorStacks[$type]; + } + + /** + * I will + * @return array + */ + public static function getErrorStacks(): array + { + return self::$errorStacks; + } + + /** + * I will return whether the ErrorStack is failed. + * If there's even only ONE single error, the Stack is assumed to have failed. + * @return bool + */ + #[Pure] public function isFailed(): bool + { + return ! $this->isSuccess(); + } + + /** + * I will return whether the ErrorStack is successful. + * If there's not even ONE single error, the Stack is considered successful. + * @return bool + */ + public function isSuccess(): bool + { + return count($this) === 0; + } + + /** + * I will add an error with the given $code and $detail to the Stack. + * + * @param string $code + * @param null $detail + * + * @return $this + */ + public function addError(string $code, $detail = null): ErrorStack + { + $error = new Error("$this->name.$code"); + $error->setDetail((array)$detail); + $this->add($error); + if (! empty($this->callback)) { + call_user_func([$this->callback['object'], $this->callback['function']], $this->getErrors()); + } + $this->notify(ErrorstackObserver::NOTIFY_ADDERROR); + + return $this; + } + + /** + * I will return all Errors that have been added to the Stack. + * + * @return \noxkiwi\core\Error[] + */ + public function getErrors(): array + { + $errors = $this->getAll(); + if (! empty($errors)) { + $this->reset(); + } + + return $errors; + } + + /** + * I will solely set the callback on the ErrorStack for the given $name and function. + * + * @param object $object + * @param string $function + */ + public function setCallback(object $object, string $function): void + { + $this->callback = compact('object', 'function'); + } + + /** + * I will return the first error in the stack. If not defined, I will return null. + * @return \noxkiwi\core\Error|null + */ + public function getFirstError(): ?Error + { + if (count($this) === 0) { + return null; + } + + return $this[0]; + } +} diff --git a/backend/class/Exception.php b/backend/class/Exception.php new file mode 100644 index 0000000..b03c3a0 --- /dev/null +++ b/backend/class/Exception.php @@ -0,0 +1,63 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.1.2 + * @link https://nox.kiwi/ + */ +abstract class Exception extends \Exception +{ + /** @var int I am the Exception's error level */ + private int $level; + /** @var mixed I am the Exception error info */ + private mixed $info; + + /** + * Create an errormessage that will stop execution of this Request. + * + * @param string $code + * @param int $level + * @param mixed $info + * + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + final public function __construct(string $code, int $level, $info = null) + { + file_put_contents('/var/www/_log/exceptions.log', chr(10) . $code, 8); + parent::__construct($code, $level); + $this->message = $code; + $this->code = $code; + $this->level = $level; + $this->info = $info; + Hook::getInstance()->fire($code); + Hook::getInstance()->fire('EXCEPTION'); + } + + /** + * I will return the Level of this Exception. + * @return int + */ + final public function getLevel(): int + { + return $this->level; + } + + /** + * I will return the info of the Exception + * @return mixed + */ + final public function getInfo(): mixed + { + return $this->info; + } +} diff --git a/backend/class/Exception/AccessDeniedException.php b/backend/class/Exception/AccessDeniedException.php new file mode 100644 index 0000000..58f79b5 --- /dev/null +++ b/backend/class/Exception/AccessDeniedException.php @@ -0,0 +1,22 @@ + + * @copyright 2021 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class AccessDeniedException extends Exception +{ +} diff --git a/backend/class/Exception/AuthenticationException.php b/backend/class/Exception/AuthenticationException.php new file mode 100644 index 0000000..dfe88bf --- /dev/null +++ b/backend/class/Exception/AuthenticationException.php @@ -0,0 +1,22 @@ +connection, $data['ftpserver']['user'], $data['ftpserver']['pass'])) { + * throw new AuthenticationException('EXCEPTION_CONSTRUCTOR_LOGIN_FAILED', E_WARNING); + * } + * + * @author Jan Nox + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class AuthenticationException extends Exception +{ +} diff --git a/backend/class/Exception/ConfigurationException.php b/backend/class/Exception/ConfigurationException.php new file mode 100644 index 0000000..d07a0a9 --- /dev/null +++ b/backend/class/Exception/ConfigurationException.php @@ -0,0 +1,22 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class ConfigurationException extends Exception +{ +} diff --git a/backend/class/Exception/ConnectionException.php b/backend/class/Exception/ConnectionException.php new file mode 100644 index 0000000..f5ee3a7 --- /dev/null +++ b/backend/class/Exception/ConnectionException.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class ConnectionException extends Exception +{ +} diff --git a/backend/class/Exception/ContextException.php b/backend/class/Exception/ContextException.php new file mode 100644 index 0000000..05844a0 --- /dev/null +++ b/backend/class/Exception/ContextException.php @@ -0,0 +1,22 @@ + + * @copyright 2021 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class ContextException extends Exception +{ +} diff --git a/backend/class/Exception/CookieException.php b/backend/class/Exception/CookieException.php new file mode 100644 index 0000000..8cd0904 --- /dev/null +++ b/backend/class/Exception/CookieException.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CookieException extends Exception +{ +} diff --git a/backend/class/Exception/CryptographyException.php b/backend/class/Exception/CryptographyException.php new file mode 100644 index 0000000..2ffd94e --- /dev/null +++ b/backend/class/Exception/CryptographyException.php @@ -0,0 +1,15 @@ + + * @copyright 2021 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CryptographyException extends Exception +{ +} diff --git a/backend/class/Exception/FilesystemException.php b/backend/class/Exception/FilesystemException.php new file mode 100644 index 0000000..ac067e0 --- /dev/null +++ b/backend/class/Exception/FilesystemException.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class FilesystemException extends Exception +{ +} diff --git a/backend/class/Exception/InvalidArgumentException.php b/backend/class/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..15ad00c --- /dev/null +++ b/backend/class/Exception/InvalidArgumentException.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class InvalidArgumentException extends Exception +{ +} diff --git a/backend/class/Exception/InvalidJsonException.php b/backend/class/Exception/InvalidJsonException.php new file mode 100644 index 0000000..e4b0386 --- /dev/null +++ b/backend/class/Exception/InvalidJsonException.php @@ -0,0 +1,48 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class InvalidJsonException extends Exception +{ + /** + * Error that occured + * + * @var string + */ + protected string $jsonError; + /** + * JSON string + * + * @var string + */ + protected string $jsonString; + + /** + * I will return a quite precise information about the error that occured + * @return string + */ + public function getJsonError(): string + { + return $this->jsonError; + } + + /** + * I will return the JSON string that was invalid + * @return string + */ + public function getJsonString(): string + { + return $this->jsonString; + } +} diff --git a/backend/class/Exception/InvalidOperationException.php b/backend/class/Exception/InvalidOperationException.php new file mode 100644 index 0000000..1d47a27 --- /dev/null +++ b/backend/class/Exception/InvalidOperationException.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class InvalidOperationException extends Exception +{ +} diff --git a/backend/class/Exception/LogicException.php b/backend/class/Exception/LogicException.php new file mode 100644 index 0000000..e07f6dd --- /dev/null +++ b/backend/class/Exception/LogicException.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class LogicException extends Exception +{ +} diff --git a/backend/class/Exception/PhpException.php b/backend/class/Exception/PhpException.php new file mode 100644 index 0000000..17e6be9 --- /dev/null +++ b/backend/class/Exception/PhpException.php @@ -0,0 +1,21 @@ + + * @license https://nox.kiwi/license + * @copyright 2019 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class PhpException extends Exception +{ +} diff --git a/backend/class/Exception/SessionException.php b/backend/class/Exception/SessionException.php new file mode 100644 index 0000000..81e3ea5 --- /dev/null +++ b/backend/class/Exception/SessionException.php @@ -0,0 +1,21 @@ + + * @license https://nox.kiwi/license + * @copyright 2019 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class SessionException extends Exception +{ +} diff --git a/backend/class/Exception/SystemComponentException.php b/backend/class/Exception/SystemComponentException.php new file mode 100644 index 0000000..7ef8847 --- /dev/null +++ b/backend/class/Exception/SystemComponentException.php @@ -0,0 +1,27 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class SystemComponentException extends Exception +{ +} diff --git a/backend/class/File.php b/backend/class/File.php new file mode 100644 index 0000000..2537ff2 --- /dev/null +++ b/backend/class/File.php @@ -0,0 +1,50 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class File +{ + /** @var string I am the name of the file this instance represents. */ + public string $name; + /** @var string|null I am the file type of the file this instance represents. */ + public ?string $type; + /** @var string|null I am the permission string of this current file. */ + public ?string $permissions; + /** @var int|null I am the number of the file in the directory listing. */ + public ?int $number; + /** @var string|null I am the username of the owner. */ + public ?string $user; + /** @var string|null I am the group name of the owner. */ + public ?string $group; + /** @var int|null I am the file size in bytes. */ + public ?int $size; + /** @var string|null I am the last modification date of the file. */ + public ?string $lastChange; + /** @var string|null I am the type (file|directory) of the current file. */ + public ?string $extension; + + /** + * File constructor. + * + * @param array $data + */ + public function __construct(array $data) + { + $this->permissions = $data['permissions'] ?? null; + $this->number = $data['number'] ?? null; + $this->user = $data['user'] ?? null; + $this->group = $data['group'] ?? null; + $this->size = $data['size'] ?? null; + $this->lastChange = $data['lastchange'] ?? null; + $this->type = $data['type'] ?? null; + } +} diff --git a/backend/class/File/FtpFile.php b/backend/class/File/FtpFile.php new file mode 100644 index 0000000..d985b3d --- /dev/null +++ b/backend/class/File/FtpFile.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class FtpFile extends File +{ +} diff --git a/backend/class/File/LocalFile.php b/backend/class/File/LocalFile.php new file mode 100644 index 0000000..e863507 --- /dev/null +++ b/backend/class/File/LocalFile.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class LocalFile extends File +{ +} diff --git a/backend/class/Filesystem.php b/backend/class/Filesystem.php new file mode 100644 index 0000000..b8d9b7d --- /dev/null +++ b/backend/class/Filesystem.php @@ -0,0 +1,339 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class Filesystem extends Singleton implements FilesystemInterface +{ + use LogTrait; + + public const TYPE_DIRECTORY = 'directory'; + public const TYPE_FILE = 'file'; + /** @var \noxkiwi\core\Errorstack Contains an instance of the errorstack class */ + protected ErrorStack $errorstack; + /** @var string I contain the error message. */ + protected string $errormessage; + /** @var array I am a temporary storage for results on the Filesystem actions */ + private array $fileCache; + + /** + * Creates the errorstack instance + */ + protected function __construct() + { + parent::__construct(); + $this->fileCache = []; + $this->errorstack = ErrorStack::getErrorStack('FILESYSTEM'); + } + + /** + * @inheritDoc + */ + public function getError(): string + { + return $this->errormessage; + } + + /** + * @inheritDoc + */ + public function fileMove(string $source, string $destination): bool + { + if (! $this->isMovable($source, $destination)) { + return false; + } + if (! $this->dirAvailable(dirname($destination))) { + $this->errorstack->addError('DESTINATION_PATH_NOT_FOUND', compact('source', 'destination')); + + return false; + } + if (! is_writable(dirname($destination))) { + $this->errorstack->addError('DESTINATION_PATH_NOT_WRITABLE', compact('source', 'destination')); + + return false; + } + $this->logWarning('Moving file ' . $source . ' to ' . $destination); + rename($source, $destination); + + return $this->fileAvailable($destination, true); + } + + /** + * I will prepare a copy/move action by validating the source and destination. + * + * @param string $source + * @param string $destination + * + * @return bool + */ + private function isMovable(string $source, string $destination): bool + { + if (! $this->fileAvailable($source)) { + $this->errorstack->addError('SOURCE_NOT_FOUND', compact('source', 'destination')); + + return false; + } + if ($this->isDirectory($source)) { + $this->errorstack->addError('SOURCE_IS_A_DIRECTORY', compact('source', 'destination')); + + return false; + } + if ($this->fileAvailable($destination)) { + $this->errorstack->addError('DESTINATION_ALREADY_EXISTS', compact('source', 'destination')); + + return false; + } + if (! $this->makePath($destination)) { + $this->errorstack->addError('DESTINATION_PATH_NOT_CREATED', compact('source', 'destination')); + + return false; + } + + return true; + } + + /** + * @inheritDoc + */ + public function fileAvailable(string $file, bool $noCache = null): bool + { + $noCache = $noCache ?? false; + if (! $noCache && isset($this->fileCache[$file])) { + return $this->fileCache[$file]; + } + $this->fileCache[$file] = file_exists($file); + + return $this->fileCache[$file]; + } + + /** + * @inheritDoc + */ + public function isDirectory(string $path): bool + { + if (! $this->fileAvailable($path, true)) { + return false; + } + + return is_dir($path); + } + + /** + * I will create several directories until the complete path is available. + * + * @param string $directory + * + * @return bool + */ + protected function makePath(string $directory): bool + { + $folders = explode('/', $directory); + array_pop($folders); + $myDir = '/'; + foreach ($folders as $folder) { + $myDir .= '/' . $folder; + if ($this->dirAvailable($myDir)) { + continue; + } + if (! $this->dirCreate($myDir)) { + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public function dirAvailable(string $directory): bool + { + if (! $this->fileAvailable($directory)) { + return false; + } + + return $this->isDirectory($directory); + } + + /** + * @inheritDoc + */ + public function dirCreate(string $directory): bool + { + if ($this->fileAvailable($directory)) { + return false; + } + if (! mkdir($directory) && ! is_dir($directory)) { + return false; + } + $this->logWarning('creating directory ' . $directory); + + return $this->fileAvailable($directory, true); + } + + /** + * @inheritDoc + */ + public function fileCopy(string $source, string $destination): bool + { + if (! $this->isMovable($source, $destination)) { + return false; + } + $this->logWarning('copying file ' . $source . ' to ' . $destination); + copy($source, $destination); + + return $this->fileAvailable($destination, true); + } + + /** + * @inheritDoc + */ + public function fileRead(string $file): string + { + if (! $this->fileAvailable($file)) { + return ''; + } + if ($this->isDirectory($file)) { + $this->errorstack->addError('DESTINATION_IS_A_DIRECTORY', compact('file')); + + return ''; + } + if (! Environment::runs(Environment::DEVELOPMENT)) { + $content = file_get_contents($file); + + return (string)$content; + } + + return (string)file_get_contents($file); + } + + /** + * @inheritDoc + */ + public function fileWrite(string $file, string $content = null): bool + { + if (! $this->makePath($file)) { + $this->errorstack->addError('DESTINATION_PATH_NOT_CREATED', compact('file')); + + return false; + } + if ($this->fileAvailable($file)) { + $this->errorstack->addError('FILE_ALREADY_EXISTS', compact('file')); + + return false; + } + if ($this->isWritable($file)) { + $this->errorstack->addError('FILE_ALREADY_EXISTS', compact('file')); + + return false; + } + file_put_contents($file, $content); + + return $this->fileAvailable($file, true); + } + + /** + * @inheritDoc + */ + public function isWritable(string $file): bool + { + return is_writable($file); + } + + /** + * @throws \noxkiwi\core\Exception + * @inheritDoc + */ + public function dirDelete(string $directory): bool + { + $files = array_diff($this->dirList($directory), ['.', '..']); + foreach ($files as $file) { + $path = $directory . '/' . $file; + if ($this->isDirectory($path)) { + $this->dirDelete($path); + } else { + $this->fileDelete($path); + } + } + $this->logWarning('Removing directory ' . $directory); + rmdir($directory); + + return ! $this->dirAvailable($directory); + } + + /** + * @inheritDoc + */ + public function dirList(string $directory): array + { + if (! $this->isDirectory($directory)) { + return []; + } + $list = scandir($directory, SCANDIR_SORT_NONE); + $myList = []; + foreach ($list as $object) { + if ($object === '.' || $object === '..') { + continue; + } + if (! is_readable($directory . '/' . $object) || ! is_writeable($directory . '/' . $object)) { + continue; + } + $myList[] = $object; + } + + return $myList; + } + + /** + * @inheritDoc + */ + public function fileDelete(string $file): bool + { + if (! $this->fileAvailable($file)) { + return false; + } + if ($this->isDirectory($file)) { + return false; + } + $this->logWarning('Deleting file ' . $file); + unlink($file); + + return ! $this->fileAvailable($file, true); + } + + /** + * @inheritDoc + */ + public function getInfo(string $file): array + { + return []; + } + + /** + * @inheritDoc + */ + public function isFile(string $file): bool + { + if (! $this->fileAvailable($file)) { + return false; + } + + return is_file($file); + } +} diff --git a/backend/class/Gate.php b/backend/class/Gate.php new file mode 100644 index 0000000..b14b930 --- /dev/null +++ b/backend/class/Gate.php @@ -0,0 +1,42 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class Gate extends Singleton implements GateInterface +{ + /** + * @inheritDoc + */ + public function open(): void + { + } + + /** + * @inheritDoc + */ + public function close(?string $reason = null): void + { + } +} diff --git a/backend/class/Gate/CidrGate.php b/backend/class/Gate/CidrGate.php new file mode 100644 index 0000000..206e405 --- /dev/null +++ b/backend/class/Gate/CidrGate.php @@ -0,0 +1,49 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CidrGate extends Gate +{ + /** + * I am the array of CIDRs that are allowed to pass the Gate. + * @var array + */ + protected array $allowedRanges = []; + + /** + * I will set the allowed ranges. + * + * @param array $ranges + */ + public function setRanges(array $ranges): void + { + $this->allowedRanges = $ranges; + } + + /** + * @inheritDoc + */ + #[Pure] public function isOpen(): bool + { + foreach ($this->allowedRanges as $allowedRange) { + if (WebHelper::isCidr($allowedRange) === true) { + return true; + } + } + + return false; + } +} diff --git a/backend/class/Gate/CliGate.php b/backend/class/Gate/CliGate.php new file mode 100644 index 0000000..9018e3d --- /dev/null +++ b/backend/class/Gate/CliGate.php @@ -0,0 +1,25 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CliGate extends Gate +{ + /** + * @inheritDoc + */ + public function isOpen(): bool + { + return PHP_SAPI === 'cli'; + } +} diff --git a/backend/class/Gate/ClosedGate.php b/backend/class/Gate/ClosedGate.php new file mode 100644 index 0000000..a2ccabf --- /dev/null +++ b/backend/class/Gate/ClosedGate.php @@ -0,0 +1,26 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 - 2021 noxkiwi + * @version 1.0.1 + * @link https://nox.kiwi/ + */ +final class ClosedGate extends Gate +{ + /** + * @inheritDoc + */ + public function isOpen(): bool + { + return false; + } +} diff --git a/backend/class/Gate/CsrfGate.php b/backend/class/Gate/CsrfGate.php new file mode 100644 index 0000000..42a9c3d --- /dev/null +++ b/backend/class/Gate/CsrfGate.php @@ -0,0 +1,110 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CsrfGate extends Gate +{ + private const CSRF_TOKEN = 'csrf'; + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return bool + */ + public function isOpen(): bool + { + $fromGet = Request::getInstance()->get(self::CSRF_TOKEN, ''); + if (self::checkCsrf($fromGet)) { + return true; + } + $fromPost = Request::getInstance()->get(self::CSRF_TOKEN, ''); + if (self::checkCsrf($fromPost)) { + return true; + } + $fromCookie = Cookie::getInstance()->get(self::CSRF_TOKEN, ''); + if (self::checkCsrf($fromCookie)) { + return true; + } + try { + Session::getInstance()->destroy(); + Cookie::getInstance()->end(); + } catch (Exception $exception) { + ErrorHandler::handleException($exception); + } + + return false; + } + + /** + * I will validate the CSRF. + * + * @param string $csrfToken + * + * @return bool + */ + private static function checkCsrf(string $csrfToken): bool + { + try { + if (Session::getInstance()->get(self::CSRF_TOKEN, '_') === $csrfToken) { + self::setCsrfToken(); + + return true; + } + } catch (Exception $exception) { + ErrorHandler::handleException($exception); + } + + return false; + } + + /** + * I will solely set the new CSRF Token. + */ + private static function setCsrfToken(): void + { + try { + $csrfToken = self::generateCsrf(); + Cookie::getInstance()->set(self::CSRF_TOKEN, $csrfToken); + Session::getInstance()->set(self::CSRF_TOKEN, $csrfToken); + } catch (Exception $exception) { + ErrorHandler::handleException($exception); + } + } + + /** + * Function generateRandomUUID2 + * + * @throws \Exception + * @return string + */ + private static function generateCsrf(): string + { + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + random_int(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0x0fff) | 0x4000, + random_int(0, 0x3fff) | 0x8000, + random_int(0, 0xffff), + random_int(0, 0xffff), + random_int(0, 0xffff) + ); + } +} diff --git a/backend/class/Gate/Fail2banGate.php b/backend/class/Gate/Fail2banGate.php new file mode 100644 index 0000000..288f7e2 --- /dev/null +++ b/backend/class/Gate/Fail2banGate.php @@ -0,0 +1,74 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class Fail2banGate extends Gate +{ + /** @var string I am the filename to the fail2ban jail. */ + private string $file; + + /** + * I will construct the Fail2banGate. + * + * @param array $options + */ + protected function __construct(array $options) + { + parent::__construct(); + $this->setFile((string)($options['file'] ?? '')); + } + + /** + * I will solely set the $file of the jail file. + * + * @param string $file + */ + private function setFile(string $file): void + { + $this->file = $file; + } + + /** + * I will solely return the path to the jail file. + * + * @return string + */ + private function getFile(): string + { + return $this->file; + } + + /** + * @inheritDoc + */ + public function isOpen(): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function close(?string $reason = null): void + { + parent::close($reason); + if (! is_writable($this->getFile())) { + return; + } + file_put_contents($this->getFile(), chr(10) . WebHelper::getClientIp(), FILE_APPEND); + } +} diff --git a/backend/class/Gate/HostnameGate.php b/backend/class/Gate/HostnameGate.php new file mode 100644 index 0000000..e94981a --- /dev/null +++ b/backend/class/Gate/HostnameGate.php @@ -0,0 +1,42 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class HostnameGate extends Gate +{ + /** + * I am the list of allowed host names. + * @var string[] + */ + private array $hostNames; + + /** + * I will set the allowed host names. + * + * @param array $hostNames + */ + public function setHostNames(array $hostNames): void + { + $this->hostNames = $hostNames ?? []; + } + + /** + * @inheritDoc + */ + public function isOpen(): bool + { + return in_array($_SERVER['HTTP_HOST'], $this->hostNames, true); + } +} diff --git a/backend/class/Gate/IpGate.php b/backend/class/Gate/IpGate.php new file mode 100644 index 0000000..950515c --- /dev/null +++ b/backend/class/Gate/IpGate.php @@ -0,0 +1,44 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class IpGate extends Gate +{ + /** + * I am the array of IP addresses that are allowed to pass the Gate. + * @var string[] + */ + private array $allowedHosts; + + /** + * I will set the allowed IPv4 addresses. + * + * @param string[] $allowedIps + */ + public function setAllowedHosts(array $allowedIps): void + { + $this->allowedHosts = $allowedIps ?? []; + } + + /** + * @inheritDoc + */ + #[Pure] public function isOpen(): bool + { + return in_array(WebHelper::getClientIp(), $this->allowedHosts, true); + } +} diff --git a/backend/class/Gate/MaintenanceGate.php b/backend/class/Gate/MaintenanceGate.php new file mode 100644 index 0000000..1634bd3 --- /dev/null +++ b/backend/class/Gate/MaintenanceGate.php @@ -0,0 +1,73 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class MaintenanceGate extends Gate +{ + public const MAINTENANCE_FILE = '.maintenance'; + public const MAINTENANCE_TEMPLATE = Path::PAGE_MAINTENANCE; + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function isOpen(): bool + { + return ! Filesystem::getInstance()->fileAvailable(self::getPath()); + } + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function open(): void + { + parent::open(); + Filesystem::getInstance()->fileDelete(self::MAINTENANCE_FILE); + } + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function close(?string $reason = null): void + { + parent::close($reason); + Filesystem::getInstance()->fileWrite(self::getPath(), $reason); + } + + /** + * I will solely return the path of the maintenance gate's trigger file. + * + * This is, if defined, located in the HOME folder or in the document root + * of the currently running app. + * + * @return string + */ + public static function getPath(): string + { + if (defined('HOME') && ! empty(HOME)) { + return HOME . self::MAINTENANCE_FILE; + } + $documentRoot = Request::getInstance()->get('DOCUMENT_ROOT'); + if (! empty($documentRoot)) { + return $documentRoot . self::MAINTENANCE_FILE; + } + + return self::MAINTENANCE_FILE; + } +} diff --git a/backend/class/Gate/ProtocolGate.php b/backend/class/Gate/ProtocolGate.php new file mode 100644 index 0000000..750753f --- /dev/null +++ b/backend/class/Gate/ProtocolGate.php @@ -0,0 +1,48 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class ProtocolGate extends Gate +{ + /** + * I am the array of CIDRs that are allowed to pass the Gate. + * @var array + */ + protected static array $allowedRanges = []; + + /** + * @inheritDoc + */ + protected function __construct(?array $options = null) + { + parent::__construct(); + self::$allowedRanges = $options ?? []; + } + + /** + * @inheritDoc + */ + #[Pure] public function isOpen(): bool + { + foreach (self::$allowedRanges as $allowedRange) { + if (WebHelper::isCidr($allowedRange) === true) { + return true; + } + } + + return false; + } +} diff --git a/backend/class/Gate/RemoteHostnameGate.php b/backend/class/Gate/RemoteHostnameGate.php new file mode 100644 index 0000000..d1cbb68 --- /dev/null +++ b/backend/class/Gate/RemoteHostnameGate.php @@ -0,0 +1,49 @@ + + * @license https://nox.kiwi/license + * @copyright 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class RemoteHostnameGate extends Gate +{ + /** + * I am the list of allowed host names. + * @var string[] + */ + private static array $remoteHostnames = []; + + /** + * @inheritDoc + */ + protected function __construct(?array $remoteHostnames = null) + { + parent::__construct(); + self::$remoteHostnames = $remoteHostnames ?? []; + } + + /** + * @inheritDoc + */ + #[Pure] public function isOpen(): bool + { + foreach (self::$remoteHostnames as $remoteHostname) { + $ipAddress = gethostbyname($remoteHostname); + if (WebHelper::getClientIp() === $ipAddress) { + return true; + } + } + + return false; + } +} diff --git a/backend/class/Helper/ArrayHelper.php b/backend/class/Helper/ArrayHelper.php new file mode 100644 index 0000000..576680e --- /dev/null +++ b/backend/class/Helper/ArrayHelper.php @@ -0,0 +1,47 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.1 + * @link https://nox.kiwi/ + */ +abstract class ArrayHelper +{ + /** + * Merges/unifies two arrays recursively + * + * @link http://stackoverflow.com/questions/25712099/php-multidimensional-array-merge-recursive + * + * @param array $array1 + * @param array $array2 + * + * @return array + */ + public static function arrayMergeRecursive(array $array1, array $array2): array + { + $merged = $array1; + foreach ($array2 as $key => $value) { + if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { + $merged[$key] = static::arrayMergeRecursive($merged[$key], $value); + } elseif (is_numeric($key)) { + if (! in_array($value, $merged, true)) { + $merged[] = $value; + } + } else { + $merged[$key] = $value; + } + } + + return $merged; + } +} diff --git a/backend/class/Helper/CryptographyHelper.php b/backend/class/Helper/CryptographyHelper.php new file mode 100644 index 0000000..0d03753 --- /dev/null +++ b/backend/class/Helper/CryptographyHelper.php @@ -0,0 +1,58 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class CryptographyHelper +{ + /** + * I will encrypt the given $decrypted data. + * + * @param string $decrypted + * @param string $key + * @param string $iv + * + * @return string + */ + public static function encrypt(string $decrypted, string $key, string $iv): string + { + return base64_encode(openssl_encrypt($decrypted, 'AES-256-CBC', $key, 0, substr($iv, 0, 16))); + } + + /** + * I will decrypt the given string. + * + * @param string $encrypted + * @param string $key + * @param string $iv + * + * @throws \noxkiwi\core\Exception\CryptographyException + * @return string + */ + public static function decrypt(string $encrypted, string $key, string $iv): string + { + $encrypted = base64_decode($encrypted); + if (! is_string($encrypted)) { + throw new CryptographyException('EXCEPTION_DECRYPT_BASEDECODEERROR', E_WARNING, $encrypted); + } + $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, substr($iv, 0, 16)); + if ($decrypted === false) { + $info = ['data' => $encrypted, 'key' => $key, 'iv' => $iv]; + throw new CryptographyException('EXCEPTION_DECRYPT_SSLDECRYPTERROR', E_WARNING, $info); + } + + return $decrypted; + } +} diff --git a/backend/class/Helper/DateHelper.php b/backend/class/Helper/DateHelper.php new file mode 100644 index 0000000..65dbf13 --- /dev/null +++ b/backend/class/Helper/DateHelper.php @@ -0,0 +1,190 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class DateHelper +{ + /** + * + */ + public const INTERVAL_DAY = 'day'; + /** + * + */ + public const INTERVAL_MONTH = 'month'; + /** + * + */ + public const INTERVAL_YEAR = 'year'; + + /** + * Returns the current date as a DB readible formatted date + * + * @example 2013-10-01 + * @return string + */ + public static function getCurrentDateAsDbdate(): string + { + return date('Y-m-d', static::getCurrentTimestamp()); + } + + /** + * Returns the timestamp of the current time + * + * @return int + */ + public static function getCurrentTimestamp(): int + { + return time(); + } + + /** + * Returns the current date as a readible format. + *
Uses the translation key DATETIME.FORMAT_DATE of your current localisation + * + * @return string + */ + public static function getCurrentDateAsReadible(): string + { + return date('d.m.Y', static::getCurrentTimestamp()); + } + + /** + * This method will return an array of timestamps that will differ from each other by the given $interval + * + * @param int $start + * @param int $end + * @param string $interval + * + * @return array + */ + public static function getIntervalArrayFromStartUntilEnd(int $start, int $end, string $interval): array + { + $intervals = []; + if (! in_array($interval, [static::INTERVAL_MONTH, static::INTERVAL_DAY, static::INTERVAL_YEAR], true)) { + echo $interval . 'Fehlt!'; + + return $intervals; + } + $lastStart = $start; + while ($start < $end) { + $newStart = strtotime(date('Y-m-d', $start) . ' +1 ' . $interval); + if ($newStart > $end) { + break; + } + $intervals[] = ['start' => $lastStart, 'end' => $start]; + $lastStart = $newStart; + } + + return $intervals; + } + + /** + * This function returns an array of unix timestamps when an article shall be invoiced & provisioned again + * + * @param int $start + * @param string $interval + * + * @return array + */ + public static function getIntervalsFromStartUntilNow(int $start, string $interval): array + { + return static::getIntervalsFromStartUntilEnd($start, static::getCurrentTimestamp(), $interval); + } + + /** + * This method will return an array of timestamps that will differ from each other by the given $interval + * + * @param int $start + * @param int $end + * @param string $interval + * + * @return array + */ + public static function getIntervalsFromStartUntilEnd(int $start, int $end, string $interval): array + { + $intervals = [$start]; + if (! in_array($interval, [static::INTERVAL_MONTH, static::INTERVAL_DAY, static::INTERVAL_YEAR], true)) { + return $intervals; + } + while ($start < $end) { + $newStart = strtotime(date('Y-m-d', $start) . ' +1 ' . $interval); + if ($newStart > $end) { + break; + } + $intervals[] = $newStart; + } + + return $intervals; + } + + /** + * Returns the current date as a DB-conform '2016-11-25 13:57:12' Format + * @example 2016-11-25 + * @return string + */ + public static function getCurrentDateTimeAsDbdate(): string + { + return date('Y-m-d H:i:s', static::getCurrentTimestamp()); + } + + /** + * Returns the current date as a DB-conform '2016-11-25 13:57:12' Format + * + * @example 2016-12-09 + * + * @param int $timestamp + * + * @return string + */ + public static function getTimestampAsDbdate(int $timestamp): string + { + return date('Y-m-d H:i:s', $timestamp); + } + + /** + * I will translate the given $date into text. + * + * @param $date + * @param bool $withTime + * + * @return string + */ + public static function timeInWords($date, bool $withTime = null): string + { + if (! $date) { + return 'N/A'; + } + $withTime = $withTime ?? true; + $timestamp = strtotime($date); + $distance = round(abs(time() - $timestamp) / 60); + if ($distance <= 1) { + $return = $distance == 0 ? 'a few seconds ago' : '1 minute ago'; + } elseif ($distance < 60) { + $return = $distance . ' minutes ago'; + } elseif ($distance < 119) { + $return = 'an hour ago'; + } elseif ($distance < 1440) { + $return = round($distance / 60.0) . ' hours ago'; + } elseif ($distance < 2880) { + $return = 'Yesterday' . ($withTime ? ' at ' . date('g:i A', $timestamp) : ''); + } elseif ($distance < 14568) { + $return = date('l, F d, Y', $timestamp) . ($withTime ? ' at ' . date('g:i A', $timestamp) : ''); + } else { + $return = date('F d ', $timestamp) . date('Y') !== date('Y', $timestamp) ? ' ' . date('Y', $timestamp) : '' . ($withTime ? ' at ' . date('g:i A', $timestamp) : ''); + } + + return $return; + } +} diff --git a/backend/class/Helper/DateTimeHelper.php b/backend/class/Helper/DateTimeHelper.php new file mode 100644 index 0000000..4d1cbf5 --- /dev/null +++ b/backend/class/Helper/DateTimeHelper.php @@ -0,0 +1,208 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class DateTimeHelper +{ + public const CONFIG_TIMEZONE = 'timeZone'; + + /** + * I will convert the given $date to the server's time zone. + * + * @param string|\DateTime $date + * + * @return \DateTime|null + */ + final public static function toServer(string|DateTime $date): ?DateTime + { + if (empty($date)) { + return null; + } + try { + return static::toTimeZone($date, new DateTimeZone(self::getServerTimeZone())); + } catch (Exception $exception) { + ErrorHandler::handleException($exception, E_USER_NOTICE); + } + + return null; + } + + /** + * @param \DateTime $date + * @param \DateTimeZone|null $timeZone + * + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return \DateTime|null + */ + final public static function toTimeZone(DateTime $date, DateTimeZone $timeZone = null): ?DateTime + { + if (empty($date)) { + return null; + } + $date->setTimezone($timeZone ?? new DateTimeZone(self::getServerTimeZone())); + + return $date; + } + + /** + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return string + */ + final public static function getServerTimeZone(): string + { + $application = Application::getInstance(); + + return $application->get(self::CONFIG_TIMEZONE, 'UTC'); + } + + /** + * I will return the formatted user time. + * + * @param null $date + * + * @return string|null + */ + final public static function user($date = null): ?string + { + $dt = static::normalize($date, true); + $dt = static::toUser($dt); + if ($dt === null) { + return ''; + } + + return $dt->format('d.m.Y H:i:s'); + } + + /** + * I will normalize the given $date into a DateTime Object. + * + * @param string|\DateTime|null $date + * + * @param bool $now + * + * @return \DateTime|null + */ + final public static function normalize(string|DateTime|null $date, bool $now = false): ?DateTime + { + if (empty($date) && $now === false) { + return null; + } + if (empty($date) && $now) { + $date = ''; + } + if ($date instanceof DateTime) { + return $date; + } + try { + $returnValue = new DateTime($date); + $returnValue->setTimezone(new DateTimeZone(self::getServerTimeZone())); + + return $returnValue; + } catch (Exception $exception) { + ErrorHandler::handleException($exception, E_USER_NOTICE); + } + + return null; + } + + /** + * @param null $date + * + * @return \DateTime|null + */ + final public static function toUser($date = null): ?DateTime + { + if (empty($date)) { + return null; + } + try { + $dt = static::normalize($date); + if ($dt === null) { + return null; + } + + return static::toTimeZone($dt, new DateTimeZone(self::getUserTimeZone())); + } catch (Exception $exception) { + ErrorHandler::handleException($exception, E_USER_NOTICE); + } + + return null; + } + + /** + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return string + */ + final public static function getUserTimeZone(): string + { + $session = Session::getInstance(); + + return $session->get(self::CONFIG_TIMEZONE, 'Europe/Berlin'); + } + + /** + * I will format the given DateTime Object in the user language's date format + * After putting it to the user time zone. + * + * @param \DateTime $date + * + * @return string + */ + final public static function toUserFormat(DateTime $date): string + { + $user = self::toUser($date); + if ($user === null) { + return ''; + } + + return $user->format('d.m.Y H:i:s'); + } + + /** + * I will return the server time in ISO. + * @return string + */ + final public static function iso(): string + { + $dt = self::normalize(null, true); + if ($dt === null) { + return ''; + } + + return $dt->format('c'); + } + + /** + * I will return the formatted server time. + * + * @param string|\DateTime|null $date + * + * @return string|null + */ + final public static function server(string|DateTime|null $date = null): ?string + { + $dt = static::normalize($date, true); + if ($dt === null) { + return ''; + } + + return $dt->format('d.m.Y H:i:s'); + } +} diff --git a/backend/class/Helper/FilesystemHelper.php b/backend/class/Helper/FilesystemHelper.php new file mode 100644 index 0000000..a89a745 --- /dev/null +++ b/backend/class/Helper/FilesystemHelper.php @@ -0,0 +1,209 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class FilesystemHelper +{ + /** + * I will return the file name of the given file. + * + * + * @param string $path + * + * @return string + */ + public static function getFileName(string $path): string + { + $filenamedata = explode('/', $path); + + return $filenamedata[count($filenamedata) - 1]; + } + + /** + * I will return the file name of the given file. + * + * + * @param string $path + * + * @return string + */ + public static function getFileExtension(string $path): string + { + $filenamedata = explode('.', $path); + + return $filenamedata[count($filenamedata) - 1]; + } + + /** + * I will return the file name of the given file. + * + * + * @param string $path + * + * @return string + */ + public static function getDirectory(string $path): string + { + $filenamedata = explode('/', $path); + if (count($filenamedata) === 1) { + return '/'; + } + unset($filenamedata[count($filenamedata) - 1]); + + return implode('/', $filenamedata); + } + + /** + * I will clean up the given $filename for further processing. + * + * @param string $filename + * + * @return string + */ + final public static function cleanFilename(string $filename): string + { + $filename = preg_replace('/[∂άαáàâãªä]/u', 'a', $filename); + $filename = preg_replace('/[∆лДΛдАÁÀÂÃÄ]/u', 'A', $filename); + $filename = preg_replace('/[ЂЪЬБъь]/u', 'b', $filename); + $filename = preg_replace('/[βвВ]/u', 'B', $filename); + $filename = preg_replace('/[çς©с]/u', 'c', $filename); + $filename = preg_replace('/[ÇС]/u', 'C', $filename); + $filename = preg_replace('/[δ]/u', 'd', $filename); + $filename = preg_replace('/[éèêëέëèεе℮ёєэЭ]/u', 'e', $filename); + $filename = preg_replace('/[ÉÈÊË€ξЄ€Е∑]/u', 'E', $filename); + $filename = preg_replace('/[₣]/u', 'F', $filename); + $filename = preg_replace('/[НнЊњ]/u', 'H', $filename); + $filename = preg_replace('/[ђћЋ]/u', 'h', $filename); + $filename = preg_replace('/[ÍÌÎÏ]/u', 'I', $filename); + $filename = preg_replace('/[íìîïιίϊі]/u', 'i', $filename); + $filename = preg_replace('/[Јј]/u', 'j', $filename); + $filename = preg_replace('/[ΚЌК]/u', 'K', $filename); + $filename = preg_replace('/[ќк]/u', 'k', $filename); + $filename = preg_replace('/[ℓ∟]/u', 'l', $filename); + $filename = preg_replace('/[Мм]/u', 'M', $filename); + $filename = preg_replace('/[ñηήηπⁿ]/u', 'n', $filename); + $filename = preg_replace('/[Ñ∏пПИЙийΝЛ]/u', 'N', $filename); + $filename = preg_replace('/[óòôõºöοФσόо]/u', 'o', $filename); + $filename = preg_replace('/[ÓÒÔÕÖθΩθОΩ]/u', 'O', $filename); + $filename = preg_replace('/[ρφрРф]/u', 'p', $filename); + $filename = preg_replace('/[®яЯ]/u', 'R', $filename); + $filename = preg_replace('/[ГЃгѓ]/u', 'r', $filename); + $filename = preg_replace('/[Ѕ]/u', 'S', $filename); + $filename = preg_replace('/[ѕ]/u', 's', $filename); + $filename = preg_replace('/[Тт]/u', 'T', $filename); + $filename = preg_replace('/[τ†‡]/u', 't', $filename); + $filename = preg_replace('/[úùûüџμΰµυϋύ]/u', 'u', $filename); + $filename = preg_replace('/[√]/u', 'v', $filename); + $filename = preg_replace('/[ÚÙÛÜЏЦц]/u', 'U', $filename); + $filename = preg_replace('/[Ψψωώẅẃẁщш]/u', 'w', $filename); + $filename = preg_replace('/[ẀẄẂШЩ]/u', 'W', $filename); + $filename = preg_replace('/[ΧχЖХж]/u', 'x', $filename); + $filename = preg_replace('/[ỲΫ¥]/u', 'Y', $filename); + $filename = preg_replace('/[ỳγўЎУуч]/u', 'y', $filename); + $filename = preg_replace('/[ζ]/u', 'Z', $filename); + $filename = preg_replace('/[‚‚]/u', ',', $filename); + $filename = preg_replace('/[`‛′’‘]/u', '\'', $filename); + $filename = preg_replace('/[″“”«»„]/u', '\'', $filename); + $filename = preg_replace('/[—–―−–‾⌐─↔→←]/u', '-', $filename); + $filename = preg_replace('/[ ]/u', ' ', $filename); + $filename = (string)str_replace(['…', '≠', '≤', '≥'], ['...', '!=', '<=', '>='], $filename); + $filename = preg_replace('/[‗≈≡]/u', '=', $filename); + $filename = (string)str_replace(['ыЫ', '℅', '₧', '™', '№', 'Ч', '‰'], ['bl', 'c/o', 'Pts', 'tm', 'No', '4', '%'], $filename); + $filename = preg_replace('/[∙•]/u', '*', $filename); + $filename = (string)str_replace(['‹', '›', '‼', '⁄', '∕', '⅞', '⅝', '⅜', '⅛'], ['<', '>', '!!', '/', '/', '7/8', '5/8', '3/8', '1/8'], $filename); + $filename = preg_replace('/[‰]/u', '%', $filename); + $filename = preg_replace('/[Љљ]/u', 'Ab', $filename); + $filename = preg_replace('/[Юю]/u', 'IO', $filename); + $filename = preg_replace('/[fifl]/u', 'fi', $filename); + $filename = preg_replace('/[зЗ]/u', '3', $filename); + $filename = (string)str_replace(['£', '₤'], ['(pounds)', '(lira)'], $filename); + $filename = preg_replace('/[‰]/u', '%', $filename); + $filename = preg_replace('/[↨↕↓↑│]/u', '|', $filename); + $filename = preg_replace('/[∞∩∫⌂⌠⌡]/u', '', $filename); + + return (string)str_replace([',', '_', '/', '\\/', ' '], '', $filename); + } + + /** + * I will return file information for the given $fileName. + * + * @param $filename + * + * @return array + */ + #[ArrayShape(['icon_class' => 'string', 'extension' => 'mixed|string', 'category' => 'string'])] public static function getFileInfo($filename): array + { + preg_match('/\.[^\.]+$', $filename, $ext); + $return = [ + 'icon_class' => '', + 'extension' => $ext[0] ?? '', + 'category' => '' + ]; + switch (strtolower($return['extension'])) { + case '.pdf': + case '.doc': + case '.rtf': + case '.txt': + case '.docx': + case '.xls': + case '.xlsx': + $return['icon_class'] = 'file-text'; + $return['category'] = 'document'; + break; + case '.png': + case '.jpg': + case '.jpeg': + case '.gif': + case '.bmp': + case '.psd': + case '.tif': + case '.tiff': + $return['icon_class'] = 'picture'; + $return['category'] = 'image'; + break; + case '.mp3': + case '.wav': + case '.wma': + case '.m4a': + case '.m3u': + $return['icon_class'] = 'music'; + $return['category'] = 'audio'; + break; + case '.3g2': + case '.3gp': + case '.asf': + case '.asx': + case '.avi': + case '.flv': + case '.m4v': + case '.mov': + case '.mp4': + case '.mpg': + case '.srt': + case '.swf': + case '.vob': + case '.wmv': + $return['icon_class'] = 'film'; + $return['category'] = 'video'; + break; + default: + $return['icon_class'] = 'file-binary'; + $return['category'] = 'other'; + break; + } + + return $return; + } +} diff --git a/backend/class/Helper/FormHelper.php b/backend/class/Helper/FormHelper.php new file mode 100644 index 0000000..9c3d53f --- /dev/null +++ b/backend/class/Helper/FormHelper.php @@ -0,0 +1,26 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class FormHelper +{ + /** + * @var string + */ + public const FIELDTYPE_YESNO = 'yesno'; + /** + * I am a list of Field types and their template names. + * + * @var array + */ + public static array $fieldTypes = ['YESNO' => 'yesno']; +} diff --git a/backend/class/Helper/FrontendHelper.php b/backend/class/Helper/FrontendHelper.php new file mode 100644 index 0000000..3594862 --- /dev/null +++ b/backend/class/Helper/FrontendHelper.php @@ -0,0 +1,218 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class FrontendHelper +{ + /** @var string[] $resources */ + public static array $resources = []; + + /** + * I will return the correct icon code for the desired icon. + * + * @param string|null $icon + * @param null $classes + * + * @return string + */ + public static function icon(string $icon = null, $classes = null): string + { + $icon ??= 'asterisk'; + $classes ??= ''; + + return ''; + } + + /** + * @param string $type + * @param string $file + * @param string|null $location + */ + public static function addResource(string $type, string $file, string $location = null): void + { + $location ??= 'footer'; + if (! in_array($type, ['js', 'css', 'png', 'jpg'])) { + return; + } + static::$resources[$location][$type][] = LinkHelper::makeUrl([ + Mvc::CONTEXT => 'resource', + Mvc::VIEW => 'file', + 'file' => static::getPath($type, $file) + ]); + } + + /** + * I will take care that on PRODUCTION environment only .minified JS will be loaded. + * + * @param string $type + * @param string $file + * + * @return string + */ + private static function getPath(string $type, string $file): string + { + if (in_array($type, ['css', 'js'], true) && Environment::runs(Environment::PRODUCTION) && ! str_contains($file, '.min')) { + $file .= '.min'; + } + + return $type . '/' . $file; + } + + /** + * @param string|null $location + * + * @return string + */ + #[Pure] public static function getResourceList(string $location = null): string + { + $location ??= 'footer'; + if (empty(static::$resources[$location])) { + return ''; + } + $return = ''; + $jsResources = static::$resources[$location]['js'] ?? []; + $cssResources = static::$resources[$location]['css'] ?? []; + foreach ($jsResources as $jsResource) { + $return .= static::getJsResource($jsResource); + } + foreach ($cssResources as $cssResource) { + $return .= static::getCssResource($cssResource); + } + + return $return; + } + + /** + * I will add a script tag to import from the given $jsResource path. + * + * @param string $jsResource + * + * @return string + */ + public static function getJsResource(string $jsResource): string + { + return << +HTML; + } + + /** + * I will add a link tag to import from the given $cssResource path. + * + * @param string $cssResource + * + * @return string + */ + public static function getCssResource(string $cssResource): string + { + return << +HTML; + } + + /** + * I will take care that the application is stopped. + * + * @param string $filePath + * @param string $httpHeader + * @param int $statusCode + * + */ + #[NoReturn] public static function outputExit(string $filePath, string $httpHeader, int $statusCode): void + { + echo static::parseFile(Path::getInheritedPath($filePath)); + header($httpHeader); + exit($statusCode); + } + + /** + * Includes the requested $file into a separate output buffer and returns the content, after parsing $data to it + * + * @param string $file + * @param mixed|null $data + * + * @return string + * @noinspection PhpUnusedParameterInspection + */ + final public static function parseFile(string $file, mixed $data = null): string + { + if ($file === '') { + return ''; + } + ob_start(); + /** @noinspection PhpIncludeInspection */ + include $file; + + return ob_get_clean(); + } + + /** + * Same as parseFile but including logic for inherited path resolving. + * + * @param string $file + * @param mixed $data + * + * @return string + */ + final public static function parse(string $file, mixed $data = null): string + { + return self::parseFile(Path::getInheritedPath($file), $data); + } + + /** + * I will output the requested file's content. + * + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + protected function viewFile(): void + { + $request = Request::getInstance(); + $type = $request->get('type', ''); + $file = $request->get('resource', ''); + if ($type === '' || $file === '') { + return; + } + [$type, $extension] = static::getTypeData($type); + $relativePath = "'resource/$type/$file.$extension"; + $absolutePath = Path::getInheritedPath($relativePath); + echo Filesystem::getInstance()->fileRead($absolutePath); + exit(); + } + + /** + * I will return information about the given $type. + * + * @param string $type + * + * @return array + */ + private static function getTypeData(string $type): array + { + return match (strtolower($type)) { + 'js' => ['js', 'js', MimeHelper::TYPE_JS], + 'png' => ['image', 'png', MimeHelper::TYPE_PNG], + 'jpg' => ['image', 'jpg', MimeHelper::TYPE_JPG], + 'gif' => ['image', 'gif', MimeHelper::TYPE_GIF], + 'css' => ['image', 'css', MimeHelper::TYPE_CSS], + default => ['', '', ''], + }; + } +} diff --git a/backend/class/Helper/JsonHelper.php b/backend/class/Helper/JsonHelper.php new file mode 100644 index 0000000..f7e743e --- /dev/null +++ b/backend/class/Helper/JsonHelper.php @@ -0,0 +1,158 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.1 + * @link https://nox.kiwi/ + */ +abstract class JsonHelper +{ + /** @var array I am the list of cached Json files. */ + private static array $cachedFiles; + + /** + * I will locate the given $file and return the JSON content as an array + * + * @param string $file + * + * @throws \noxkiwi\core\Exception\InvalidJsonException + * @return mixed + */ + public static function decodeFileToArray(string $file): mixed + { + $simpleName = $file; + if (empty($file)) { + return null; + } + if (isset (static::$cachedFiles[$file])) { + return static::$cachedFiles[$file]; + } + try { + if (! Filesystem::getInstance()->fileAvailable($file)) { + return null; + } + static::$cachedFiles[$simpleName] = static::decodeStringToArray( + Filesystem::getInstance()->fileRead($file) + ); + } catch (Exception) { + throw new InvalidJsonException("$file is not valid JSON.", E_WARNING); + } + + return static::$cachedFiles[$simpleName]; + } + + /** + * I will convert the given $json string into an array + * + * @param string $json + * + * @throws \noxkiwi\core\Exception\InvalidJsonException + * @return mixed + */ + public static function decodeStringToArray(string $json): mixed + { + try { + $jsonResult = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $exception) { + throw new InvalidJsonException('EXCEPTION_DECODESTRINGTOARRAY_INVALIDJSON', E_WARNING, $exception); + } + if ($jsonResult === null) { + $info = ['json' => $json, 'error' => static::getJsonError()]; + throw new InvalidJsonException('EXCEPTION_DECODESTRINGTOARRAY_INVALIDJSON', E_WARNING, $info); + } + + return $jsonResult; + } + + /** + * I will return a statement on what was wrong with the last JSON_DECODE call. + * @return string + */ + public static function getJsonError(): string + { + return match (json_last_error()) { + JSON_ERROR_NONE => ' - No errors', + JSON_ERROR_DEPTH => ' - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => ' - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => ' - Unexpected control character found', + JSON_ERROR_SYNTAX => ' - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => ' - Malformed UTF-8 characters, possibly incorrectly encoded', + default => ' - Unknown error', + }; + } + + /** + * I will locate the given $file and return the JSON as an object + * + * @param string $file + * + * @throws \noxkiwi\core\Exception + * @throws \noxkiwi\core\Exception\FilesystemException + * @throws \noxkiwi\core\Exception\InvalidJsonException + * @return mixed + */ + public static function decodeFileToObject(string $file): mixed + { + if (! Filesystem::getInstance()->fileAvailable($file)) { + throw new FilesystemException('EXCEPTION_DECODESTRINGTOARRAY_INVALIDJSON', E_WARNING, ['file' => $file]); + } + + return static::decodeStringToObject(file_get_contents($file)); + } + + /** + * I will return the given $json string as an object + * + * @param string $json + * + * @throws \noxkiwi\core\Exception\InvalidJsonException + * @return mixed + */ + public static function decodeStringToObject(string $json): mixed + { + try { + $json = json_decode($json, true, 512, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT); + } catch (JsonException $exception) { + $info = ['json' => $json, 'error' => static::getJsonError(), 'inner' => $exception]; + throw new InvalidJsonException('INVALID_JSON_CAUGHT', E_WARNING, $info); + } + if ($json === null) { + $info = ['json' => $json, 'error' => static::getJsonError()]; + throw new InvalidJsonException('INVALID_JSON', E_WARNING, $info); + } + + return $json; + } + + /** + * I will encode the given $data to a valid JSON string and return it. + * + * @param mixed $data + * + * @return string + */ + public static function encode(mixed $data): string + { + try { + return (string)json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } catch (JsonException) { + return ''; + } + } +} diff --git a/backend/class/Helper/LinkHelper.php b/backend/class/Helper/LinkHelper.php new file mode 100644 index 0000000..ce402e2 --- /dev/null +++ b/backend/class/Helper/LinkHelper.php @@ -0,0 +1,138 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class LinkHelper +{ + /** @var bool Encrypt links to obfuscate the underlying mvc? */ + public static bool $encryptLinks = false; + /** @var string The password */ + public static string $secret = 'k26p555ug9d72j028f2dprknf'; + + /** + * I will decode the given $params string into a queryparam string. + * + * @param string $encrypted + * + * @return string + */ + public static function decryptLink(string $encrypted): string + { + try { + $encrypted = substr($encrypted, 1, strlen($encrypted)); + + return (string)str_replace('?', '', CryptographyHelper::decrypt($encrypted, static::$secret, static::$secret)); + } catch (Exception) { + return ''; + } + } + + /** + * I will return the url that is either for the MVC pattern or a real one. + * + * @param array|string $url + * + * @return string + */ + public static function get(array|string $url): string + { + if (is_array($url)) { + return static::makeUrl($url); + } + if (is_string($url)) { + return $url; + } + + return '#'; + } + + /** + * I will return a URL that overrides or enhances the current response with the given $params data. + * I will call makeParameters to ensure that no illegal arguments will be part of the URL. + * + * @param array|null $parameters + * + * @return string + */ + public static function makeUrl(array $parameters = null): string + { + $parameters ??= []; + $response = Response::getInstance(); + $original = [ + Mvc::CONTEXT => $response->get(Mvc::CONTEXT), + Mvc::VIEW => $response->get(Mvc::VIEW), + Mvc::ACTION => $response->get(Mvc::ACTION), + ]; + $parameters = ArrayHelper::arrayMergeRecursive($original, $parameters); + + return static::makeParameters($parameters); + } + + /** + * I will return the given $params as a URI parameter string. + * + * @param array $params + * + * @return string + */ + public static function makeParameters(array $params): string + { + return static::encryptLink('?' . http_build_query($params)); + } + + /** + * I will encode the given $params string with base64 if not on development. + * + * @param string $decrypted + * + * @return string + */ + private static function encryptLink(string $decrypted): string + { + if (! static::$encryptLinks) { + return $decrypted; + } + + return CryptographyHelper::encrypt($decrypted, static::$secret, static::$secret); + } + + /** + * I will send the user to the given page. + * + * The $statusCode defaults to: + * @see \noxkiwi\core\Helper\WebHelper::HTTP_MOVED_PERMANENTLY + * + * @param array|string $redirect + * @param int $statusCode + */ + #[NoReturn] public static function forward(array|string $redirect, int $statusCode = WebHelper::HTTP_MOVED_PERMANENTLY): void + { + if (is_array($redirect)) { + $redirect = LinkHelper::makeUrl($redirect); + } + if (! is_string($redirect)) { + return; + } + header("Location: $redirect"); + header("HTTP/1.0 $statusCode " . WebHelper::$responseCodes[$statusCode]); + exit(WebHelper::HTTP_PERMANENT_REDIRECT); + } +} diff --git a/backend/class/Helper/MimeHelper.php b/backend/class/Helper/MimeHelper.php new file mode 100644 index 0000000..759c1b9 --- /dev/null +++ b/backend/class/Helper/MimeHelper.php @@ -0,0 +1,115 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class MimeHelper +{ + // FILE EXTENSIONS + public const EXTENSION_JPG = 'jpg'; + public const EXTENSION_JPEG = 'jpeg'; + public const EXTENSION_PDF = 'pdf'; + public const EXTENSION_PNG = 'png'; + public const EXTENSION_GIF = 'gif'; + public const EXTENSION_JSON = 'json'; + public const EXTENSION_CSS = 'css'; + public const EXTENSION_JS = 'js'; + // MIME TYPES + public const TYPE_JSON = 'text/json'; + public const TYPE_PDF = 'application/pdf'; + public const TYPE_FIF = 'image/fif'; + public const TYPE_IEF = 'image/ief'; + public const TYPE_GIF = 'image/gif'; + public const TYPE_PNG = 'image/png'; + public const TYPE_JPG = 'image/jpg'; + public const TYPE_JPG_2000 = 'image/jpg2000'; + public const TYPE_JPEG_2000 = 'image/jpeg2000'; + public const TYPE_JPEG = 'image/jpeg'; + public const TYPE_TIFF = 'image/tiff'; + public const TYPE_VASA = 'image/vasa'; + public const TYPE_X_ICON = 'image/x-icon'; + public const TYPE_JS = 'text/javascript'; + public const TYPE_CSS = 'text/css'; + public const TYPE_FORCE_DOWNLOAD = 'application/force-download'; + /** + * I am the list of file extensions and their expected MIME Types + * @var array + */ + public static array $resources = [ + 'js' => ['js', 'js', self::TYPE_JS], + 'css' => ['css', 'css', self::TYPE_CSS], + 'jpg' => ['image', 'jpg', self::TYPE_JPG], + 'gif' => ['image', 'gif', self::TYPE_GIF], + 'png' => ['image', 'png', self::TYPE_PNG] + ]; + + /** + * I will send the Content-Type header. + * + * @param string $type + */ + public static function sendHeaders(string $type): void + { + if (headers_sent()) { + return; + } + $contentType = static::getResourceFromType($type)[2]; + header('Content-Type: ' . $contentType); + } + + /** + * I will return the resources of the given $type. + * + * @param string $type + * + * @return array|null + */ + public static function getResourceFromType(string $type): ?array + { + return static::$resources[strtolower($type)] ?? null; + } + + /** + * I will utilize the lowercase variant of $extension to determine a matching mime type. + * + * @param string $extension + * @param string $default + * + * @return string + */ + final public static function getFromExtension(string $extension, string $default): string + { + return match (strtolower($extension)) { + self::EXTENSION_PDF => self::TYPE_PDF, + self::EXTENSION_GIF => self::TYPE_GIF, + self::EXTENSION_PNG => self::TYPE_PNG, + self::EXTENSION_JPEG, self::EXTENSION_JPG => self::TYPE_JPG, + self::EXTENSION_JSON => self::TYPE_JSON, + self::EXTENSION_CSS => self::TYPE_CSS, + self::EXTENSION_JS => self::TYPE_JS, + default => $default, + }; + } + + /** + * I will return the Mime type according to the given $filePath. + * + * @param string $filePath + * + * @return string + */ + final public static function getFromFile(string $filePath): string + { + $exploded = explode('.', $filePath); + + return self::getFromExtension(end($exploded), self::TYPE_FORCE_DOWNLOAD); + } +} diff --git a/backend/class/Helper/StringHelper.php b/backend/class/Helper/StringHelper.php new file mode 100644 index 0000000..9fd24ac --- /dev/null +++ b/backend/class/Helper/StringHelper.php @@ -0,0 +1,44 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class StringHelper +{ + /** + * Interpolates context values into the message placeholders. + * + * @param string $message + * @param array|null $context + * + * @return string + * @link https://www.php-fig.org/psr/psr-3/ + */ + final public static function interpolate(string $message, array $context = null): string + { + if (empty($context)) { + return $message; + } + $replace = []; + foreach ($context as $key => $val) { + if (! is_array($val) && (! is_object($val) || method_exists($val, '__toString'))) { + $replace['{' . $key . '}'] = $val; + } + } + + return strtr($message, $replace); + } +} diff --git a/backend/class/Helper/WebHelper.php b/backend/class/Helper/WebHelper.php new file mode 100644 index 0000000..4cb2a1b --- /dev/null +++ b/backend/class/Helper/WebHelper.php @@ -0,0 +1,202 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class WebHelper +{ + public const METHOD_POST = 'POST'; + public const METHOD_GET = 'GET'; + public const METHOD_PUT = 'PUT'; + public const METHOD_DELETE = 'DELETE'; + public const METHODS = [ + self::METHOD_POST, + self::METHOD_GET, + self::METHOD_PUT, + self::METHOD_DELETE + ]; + public const PROTOCOL_HTTP = 'http'; + public const PROTOCOL_HTTPS = 'https'; + public const PROTOCOLS = [ + self::PROTOCOL_HTTP, + self::PROTOCOL_HTTPS + ]; + public const HTTP_OKAY = 200; + public const HTTP_MULTIPLE_CHOICES = 300; + public const HTTP_MOVED_PERMANENTLY = 301; + public const HTTP_FOUND = 302; + public const HTTP_SEE_OTHER = 303; + public const HTTP_NOT_MODIFIED = 304; + public const HTTP_USE_PROXY = 305; + public const HTTP_SWITCH_PROXY = 306; + public const HTTP_TEMPORARY_REDIRECT = 307; + public const HTTP_PERMANENT_REDIRECT = 308; + public const HTTP_BAD_REQUEST = 400; + public const HTTP_UNAUTHORIZED = 401; + public const HTTP_PAYMENT_REQUIRED = 402; + public const HTTP_FORBIDDEN = 403; + public const HTTP_NOT_FOUND = 404; + public const HTTP_METHOD_NOT_ALLOWED = 405; + public const HTTP_NOT_ACCEPTABLE = 406; + public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + public const HTTP_REQUEST_TIMEOUT = 408; + public const HTTP_CONFLICT = 409; + public const HTTP_GONE = 410; + public const HTTP_LENGTH_REQUIRED = 411; + public const HTTP_PRECONDITION_FAILED = 412; + public const HTTP_PAYLOAD_TOO_LARGE = 413; + public const HTTP_URI_TOO_LONG = 414; + public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + public const HTTP_RANGE_NOT_SATISFIABLE = 416; + public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_MISDIRECTED_REQUEST = 421; + public const HTTP_UNPROCESSABLE_ENTITY = 422; + public const HTTP_LOCKED = 423; + public const HTTP_FAILED_DEPENDENCY = 424; + public const HTTP_TOO_EARLY = 425; + public const HTTP_UPGRADE_REQUIRED = 426; + public const HTTP_PRECONDITION_REQUIRED = 428; + public const HTTP_TOO_MANY_REQUESTS = 429; + public const HTTP_REQUESTHEADER_FIELDS_TOO_LARGE = 431; + public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + public const HTTP_TEAPOT = 418; + public const HTTP_POLICY_NOT_FULFILLED = 420; + public const HTTP_NO_RESPONSE = 444; + public const HTTP_RETIRED = 449; + public const HTTP_CLIENT_CLOSED = 499; + public const HTTP_SERVER_ERROR = 500; + public const HTTP_NOT_IMPLEMENTED = 501; + public const HTTP_BAD_GATEWAY = 502; + public const HTTP_SERVICE_UNAVAILABLE = 503; + public const HTTP_GATEWAY_TIMEOUT = 504; + public const HTTP_VERSION_NOT_SUPPORTED = 505; + public const HTTP_VARIANT_ALSO_NEGOTIATES = 506; + public const HTTP_INSUFFICIENT_STORAGE = 507; + public const HTTP_LOOP_DETECTED = 508; + public const HTTP_BANDWIDTH_LIMIT_EXCEEDED = 509; + public const HTTP_NOT_EXTENDED = 510; + public const HTTP_NETWORK_AUTH_REQUIRED = 511; + /** + * I am a list of HTTP status codes and their names. + * + * @var array + */ + public static array $responseCodes = [ + 100 => 'CONTINUE', + 101 => 'SWITCHING_PROTOCOLS', + 102 => 'PROCESSING', + self::HTTP_OKAY => 'OK', + 201 => 'CREATED', + 202 => 'ACCEPTED', + 203 => 'NON_AUTHORITATIVE_INFORMATION', + 204 => 'NO_CONTENT', + 205 => 'RESET_CONTENT', + 206 => 'PARTIAL_CONTENT', + 207 => 'MULTI_STATUS', + 300 => 'MULTIPLE_CHOICES', + self::HTTP_MOVED_PERMANENTLY => 'MOVED_PERMANENTLY', + 302 => 'FOUND', + 303 => 'SEE_OTHER', + 304 => 'NOT_MODIFIED', + 305 => 'USE_PROXY', + 306 => 'SWITCH_PROXY', + 307 => 'TEMPORARY_REDIRECT', + 400 => 'BAD_REQUEST', + 401 => 'UNAUTHORIZED', + 402 => 'PAYMENT_REQUIRED', + self::HTTP_FORBIDDEN => 'FORBIDDEN', + 404 => 'NOT_FOUND', + 405 => 'METHOD_NOT_ALLOWED', + 406 => 'NOT_ACCEPTABLE', + 407 => 'PROXY_AUTHENTICATION_REQUIRED', + 408 => 'REQUEST_TIMEOUT', + self::HTTP_CONFLICT => 'CONFLICT', + 410 => 'GONE', + 411 => 'LENGTH_REQUIRED', + 412 => 'PRECONDITION_FAILED', + 413 => 'REQUEST_ENTITY_TOO_LARGE', + 414 => 'REQUEST_URI_TOO_LONG', + 415 => 'UNSUPPORTED_MEDIA_TYPE', + 416 => 'REQUESTED_RANGE_NOT_SATISFIABLE', + 417 => 'EXPECTATION_FAILED', + 418 => 'IM_A_TEAPOT', + 422 => 'UNPROCESSABLE_ENTITY', + 423 => 'LOCKED', + 424 => 'FAILED_DEPENDENCY', + 425 => 'UNORDERED_COLLECTION', + 426 => 'UPGRADE_REQUIRED', + 449 => 'RETRY_WITH', + 450 => 'BLOCKED_BY_WINDOWS_PARENTAL_CONTROLS', + 451 => 'BLOCKED_FOR_LEGAL_REASONS', + self::HTTP_SERVER_ERROR => 'INTERNAL_SERVER_ERROR', + 501 => 'NOT_IMPLEMENTED', + 502 => 'BAD_GATEWAY', + 503 => 'SERVICE_UNAVAILABLE', + 504 => 'GATEWAY_TIMEOUT', + 505 => 'HTTP_VERSION_NOT_SUPPORTED', + 506 => 'VARIAN_ALSO_NEGOTIATES', + 507 => 'INSUFFICIENT_STORAGE', + 509 => 'BANDWIDTH_LIMIT_EXCEEDED', + 510 => 'NOT_EXTENDED' + ]; + + /** + * I will return whether the given $ipAddress is part of the given $cidr subnet. + * + * @param string $cidr + * @param string|null $ipAddress + * + * @return bool + */ + #[Pure] public static function isCidr(string $cidr, ?string $ipAddress = null): bool + { + if ($ipAddress === null) { + $ipAddress = static::getClientIp(); + } + [$net, $mask] = explode('/', $cidr); + $ipNet = ip2long($net); + $ipMask = ~((1 << 32 - (int)$mask) - 1); + $ipIp = ip2long($ipAddress); + + return ($ipIp & $ipMask) == ($ipNet & $ipMask); + } + + /** + * I will return the client's IP address. + * + * @return string IP address string + */ + public static function getClientIp(): string + { + if (getenv('HTTP_CLIENT_IP')) { + return getenv('HTTP_CLIENT_IP'); + } + if (getenv('HTTP_X_FORWARDED_FOR')) { + return getenv('HTTP_X_FORWARDED_FOR'); + } + if (getenv('HTTP_X_FORWARDED')) { + return getenv('HTTP_X_FORWARDED'); + } + if (getenv('HTTP_FORWARDED_FOR')) { + return getenv('HTTP_FORWARDED_FOR'); + } + if (getenv('HTTP_FORWARDED')) { + return getenv('HTTP_FORWARDED'); + } + if (getenv('REMOTE_ADDR')) { + return getenv('REMOTE_ADDR'); + } + + return 'UNKNOWN'; + } +} diff --git a/backend/class/Hook.php b/backend/class/Hook.php new file mode 100644 index 0000000..68216a6 --- /dev/null +++ b/backend/class/Hook.php @@ -0,0 +1,16 @@ + + * @license https://nox.kiwi/license + * @copyright 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class Hook extends \noxkiwi\hook\Hook +{ +} diff --git a/backend/class/Interfaces/AppInterface.php b/backend/class/Interfaces/AppInterface.php new file mode 100644 index 0000000..aae6775 --- /dev/null +++ b/backend/class/Interfaces/AppInterface.php @@ -0,0 +1,27 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.1 + * @link https://nox.kiwi/ + */ +interface AppInterface +{ + /** + * I will run the application. + * This consists of preparing Request and Response. + * The Request determines the Context to run. + * The Context will then decide the different methods to execute: + * - Action + * protected function actionDeleteEntry() : void {} + * - View + * protected function viewList() : void {} + */ + public function run(): void; +} diff --git a/backend/class/Interfaces/AuthInterface.php b/backend/class/Interfaces/AuthInterface.php new file mode 100644 index 0000000..7b57e0a --- /dev/null +++ b/backend/class/Interfaces/AuthInterface.php @@ -0,0 +1,51 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.1.1 + * @link https://nox.kiwi/ + */ +interface AuthInterface +{ + /** + * I will use the $username and $password to authenticate a user. + *
Returns an array of valid user data if succeeded + *
Returns an empty array if authentication failed + * + * @param string $userName Try authentication for this user... + * @param string $password ... using this password + * + * @return array Array of user information. Is EMPTY on authentication failure + */ + public function authenticate(string $userName, string $password): array; + + /** + * I will return the password for the given $username $password combination + *
You may want to create your own hashing algo using this method. + *
This method is public to be accessible for contexts and models (e.g. automatic user creation, password + * resetting) + * + * @param string $userName The username to hash + * @param string $password The password to hash + * + * @return string The hashed combination of $username and $password + */ + public function passwordMake(string $userName, string $password): string; + + /** + * I will return a URL that will ask the client to login. + * + * @param Request $request + * + * @return string + */ + public function getLoginUrl(Request $request): string; +} diff --git a/backend/class/Interfaces/ContextInterface.php b/backend/class/Interfaces/ContextInterface.php new file mode 100644 index 0000000..6861d94 --- /dev/null +++ b/backend/class/Interfaces/ContextInterface.php @@ -0,0 +1,25 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +interface ContextInterface +{ + /** + * I will dispatch the given $request and return the resulting Response instance. + * Dispatching the request on a context side consists of performing each (backend and frontend) controller. + * + * @param \noxkiwi\core\Request $request + */ + public function dispatch(Request $request): void; +} diff --git a/backend/class/Interfaces/CookieInterface.php b/backend/class/Interfaces/CookieInterface.php new file mode 100644 index 0000000..8dda059 --- /dev/null +++ b/backend/class/Interfaces/CookieInterface.php @@ -0,0 +1,29 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2020 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +interface CookieInterface extends DatacontainerInterface +{ + /** + * I will start the Cookie and set the sessionid if not set yet. + * + * @return \noxkiwi\core\Cookie + */ + public function start(): Cookie; + + /** + * I will unset any data in the Cookie. + */ + public function end(): void; +} diff --git a/backend/class/Interfaces/DatacontainerInterface.php b/backend/class/Interfaces/DatacontainerInterface.php new file mode 100644 index 0000000..151e812 --- /dev/null +++ b/backend/class/Interfaces/DatacontainerInterface.php @@ -0,0 +1,88 @@ +get('A>B>C>UserName', 'John.Doe'); + * + * @examle Classical approach using arrays: + * $data = json_decode($curlResponse, true); + * if(empty ($data['A'])) { + * return $default; + * } + * if(empty ($data['A']['B'])) { + * return $default; + * } + * if(empty ($data['A']['B']['C'])) { + * return $default; + * } + * if(empty ($data['A']['B']['C']['UserName'])) { + * return $default; + * } + * return $data['A']['B']['C']['UserName']; + * + * + * @author Jan Nox + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.1.2 + * @link https://nox.kiwi/ + */ +interface DatacontainerInterface +{ + /** + * Adds the given KEY => VALUE array to the current set of data + * + * @param array $data + */ + public function add(array $data): void; + + /** + * I will return the value of the given key. Either pass a direct name, or use a tree>to>navigate through the data set + *
->get('my>config>key') + * + * @param string|null $key + * @param mixed|null $default + * + * @return mixed + */ + public function get(string $key = null, mixed $default = null): mixed; + + /** + * Stores the given $data value under the given $key in this instance's data property. + * + * @param string $key + * @param mixed $data + */ + public function set(string $key, mixed $data): void; + + /** + * Removes the given $key from this instance's data set. + * + * @param string $key + */ + public function remove(string $key): void; + + /** + * Returns true if there is a value with name $key in this instance's data set. + * + * @param string $key + * + * @return bool + */ + public function exists(string $key): bool; + + /** + * I will simply override the entire data property with the given $data array. + * + * @param array $data + */ + public function put(array $data): void; +} diff --git a/backend/class/Interfaces/FilesystemInterface.php b/backend/class/Interfaces/FilesystemInterface.php new file mode 100644 index 0000000..871e602 --- /dev/null +++ b/backend/class/Interfaces/FilesystemInterface.php @@ -0,0 +1,155 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +interface FilesystemInterface +{ + /** + * Returns true if $file exists. + * + * @param string $file + * @param bool $noCache + * + * @return bool + */ + public function fileAvailable(string $file, bool $noCache = null): bool; + + /** + * Returns true if $file could be deleted. + * + * @param string $file + * + * @return bool + */ + public function fileDelete(string $file): bool; + + /** + * Returns true if $source could be moved to $destination. + * + * @param string $source + * @param string $destination + * + * @return bool + */ + public function fileMove(string $source, string $destination): bool; + + /** + * Returns true if $file could be copied to $destination + * + * @param string $source + * @param string $destination + * + * @return bool + */ + public function fileCopy(string $source, string $destination): bool; + + /** + * Returns the content of $file + * + * @param string $file + * + * @return string + */ + public function fileRead(string $file): string; + + /** + * Returns true if $countent could be written to $file + * + * @param string $file + * @param string|null $content + * + * @return bool + */ + public function fileWrite(string $file, string $content = null): bool; + + /** + * I will return true if the given $file is writable. + * + * @param string $file + * + * @return bool + */ + public function isWritable(string $file): bool; + + /** + * Returns true if $directory exists + * + * @param string $directory + * + * @return bool + */ + public function dirAvailable(string $directory): bool; + + /** + * Returns true if $directory could be created + * + * @param string $directory + * + * @return bool + */ + public function dirCreate(string $directory): bool; + + /** + * I will delete the given $directory + * + * @param string $directory + * + * @return bool + */ + public function dirDelete(string $directory): bool; + + /** + * Returns the list of objects in $directory. Returns an empty array if $directory does not exist + * + * @param string $directory + * + * @return array + * @access public + */ + public function dirList(string $directory): array; + + /** + * Returns true if $path is a directory + * + * @param string $path + * + * @return bool + * @access public + */ + public function isDirectory(string $path): bool; + + /** + * Returns an array of info about $file + * + * @param string $file + * + * @return array + * @access public + */ + public function getInfo(string $file): array; + + /** + * Returns true if the given $file actually IS a file + * + * @param string $file + * + * @return bool + */ + public function isFile(string $file): bool; + + /** + * Returns the error message + * + * @return string + */ + public function getError(): string; +} diff --git a/backend/class/Interfaces/GateInterface.php b/backend/class/Interfaces/GateInterface.php new file mode 100644 index 0000000..f8cd37d --- /dev/null +++ b/backend/class/Interfaces/GateInterface.php @@ -0,0 +1,33 @@ + + * @license https://nox.kiwi/license + * @copyright 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +interface GateInterface +{ + /** + * I will check for this Gate class' requirements and perform an action if the gate is closed. + * @return bool + */ + public function isOpen(): bool; + + /** + * I will simply open the gate. + */ + public function open(): void; + + /** + * I will simply close the gate. + * + * @param string|null $reason I am the reason why the gate has been closed. + */ + public function close(?string $reason = null): void; +} diff --git a/backend/class/Interfaces/RequestInterface.php b/backend/class/Interfaces/RequestInterface.php new file mode 100644 index 0000000..61c8c8a --- /dev/null +++ b/backend/class/Interfaces/RequestInterface.php @@ -0,0 +1,23 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2020 noxkiwi + * @version 1.1.0 + * @link https://nox.kiwi/ + */ +interface RequestInterface extends DatacontainerInterface +{ + /** + * I will build a new Request and fill it with the core data. + * @return \noxkiwi\core\Request + */ + public function build(): Request; +} diff --git a/backend/class/Interfaces/ResponseInterface.php b/backend/class/Interfaces/ResponseInterface.php new file mode 100644 index 0000000..12cd9e0 --- /dev/null +++ b/backend/class/Interfaces/ResponseInterface.php @@ -0,0 +1,16 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +interface ResponseInterface extends DatacontainerInterface +{ +} diff --git a/backend/class/Interfaces/SessionInterface.php b/backend/class/Interfaces/SessionInterface.php new file mode 100644 index 0000000..21823b1 --- /dev/null +++ b/backend/class/Interfaces/SessionInterface.php @@ -0,0 +1,45 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +interface SessionInterface extends DatacontainerInterface +{ + /** + * I will utilize the given $data to construct a new Session. + * May this be bound to a PHP Session, a Cache-driven Session or a dummy one. + * + * @param array $data + * + * @return \noxkiwi\core\Session + */ + public function start(array $data): Session; + + /** + * I will have the current Session be destroyed. + */ + public function destroy(): void; + + /** + * I will identify the Client and load the Session data to the instance. + *
If identified successfully, I will return TRUE + *
In any other case I will return FALSE + * + * @return bool + */ + public function identify(): bool; +} diff --git a/backend/class/Observer/ErrorstackObserver.php b/backend/class/Observer/ErrorstackObserver.php new file mode 100644 index 0000000..dd7d84f --- /dev/null +++ b/backend/class/Observer/ErrorstackObserver.php @@ -0,0 +1,47 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +class ErrorstackObserver extends Observer +{ + public const NOTIFY_ADDERROR = 'adderror'; + public const NOTIFY_ADDINSTANCE = 'addinstance'; + /** + * Contains the count of errors that occured + * + * @var int + */ + public static int $countErrors = 0; + /** + * Contains the amount of errorstack instances that were created + * + * @var int + */ + public static int $countInstances = 0; + + /** + * @inheritDoc + */ + public function update(ObservableInterface $observable, string $type): void + { + if ($type === self::NOTIFY_ADDERROR) { + static::$countErrors++; + } + if ($type === self::NOTIFY_ADDINSTANCE) { + static::$countInstances++; + } + } +} + diff --git a/backend/class/Path.php b/backend/class/Path.php new file mode 100644 index 0000000..f31a3e9 --- /dev/null +++ b/backend/class/Path.php @@ -0,0 +1,101 @@ + + * @license https://nox.kiwi/license + * @copyright 2019 - 2021 noxkiwi + * @version 1.0.2 + * @link https://nox.kiwi/ + */ +abstract class Path +{ + // CONFIG + public const CONFIG_DIR = 'config/'; + public const CONFIG_APPLICATION = self::CONFIG_DIR . 'app.json'; + public const CONFIG_CONTEXT_DIR = self::CONFIG_DIR . 'context/'; + public const CONFIG_URL_REWRITE = self::CONFIG_DIR . 'urlrewrite.json'; + // FRONTEND + public const FRONTEND_DIR = 'frontend/'; + public const VIEW_DIR = self::FRONTEND_DIR . 'view/'; + public const TEMPLATE_DIR = self::FRONTEND_DIR . 'template/'; + public const PAGE_DIR = self::FRONTEND_DIR . 'page/'; + public const TEMPLATE_FILE = 'template.php'; + // PAGES + public const PAGE_403 = self::PAGE_DIR . 'notallowed.php'; + public const PAGE_404 = self::PAGE_DIR . 'notfound.php'; + public const PAGE_500 = self::PAGE_DIR . 'error.php'; + public const PAGE_MAINTENANCE = self::PAGE_DIR . 'maintenance.php'; + // RESOURCES + public const RESOURCES_DIR = 'resources/'; + // RESOURCES + public const LOG_DIR = '/var/www/_log/'; + /** @var string Absolute path to the web root of the application. */ + public static string $webRoot = ''; + // RESOURCES + /** @var string Absolute path to the vendor folder. */ + public static string $vendorDir = ''; + // LOG + /** @var string I am the hostname that shall be used for all resources. */ + public static string $resourceHost = ''; + /** @var string[] I am a cache for inherited paths to store during runtime */ + private static array $inheritedPaths = []; + + /** + * Get path of file (in APP dir OR in core dir) if it exists there - throws error if neither + * + * @param string $file + * + * @return string + */ + final public static function getInheritedPath(string $file): string + { + try { + $fileSystem = Filesystem::getInstance(); + } catch (SingletonException $exception) { + ErrorHandler::handleException($exception); + + return ''; + } + if (isset(static::$inheritedPaths[$file])) { + return static::$inheritedPaths[$file]; + } + $fileName = self::getHomeDir() . $file; + if ($fileSystem->fileAvailable($fileName)) { + static::$inheritedPaths[$file] = $fileName; + + return static::$inheritedPaths[$file]; + } + $fileName = static::$vendorDir . "noxkiwi/core/$file"; + if ($fileSystem->fileAvailable($fileName)) { + static::$inheritedPaths[$file] = $fileName; + + return static::$inheritedPaths[$file]; + } + + return ''; + } + + /** + * Returns the directory where the App must be stored in + *
This method relies on the static vendorDir + * + * @param string|null $vendor + * @param string|null $app + * + * @return string + */ + #[Pure] final public static function getHomeDir(string $vendor = null, string $app = null): string + { + $vendor ??= App::getVendor(); + $app ??= App::getApp(); + + return static::$vendorDir . "$vendor/$app/"; + } +} diff --git a/backend/class/Request.php b/backend/class/Request.php new file mode 100644 index 0000000..86b7b94 --- /dev/null +++ b/backend/class/Request.php @@ -0,0 +1,108 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.2 + * @link https://nox.kiwi/ + */ +abstract class Request implements RequestInterface +{ + use DatacontainerTrait; + use LanguageImprovementTrait; + + public const REQUEST_HTTP = WebHelper::PROTOCOL_HTTPS; + public const REQUEST_CLI = 'cli'; + /** @var \noxkiwi\core\Request I am the instance. */ + private static Request $inst; + + /** + * If the single instance does not exist, create it. + * Return the single instance then. + * + * @noinspection PhpMissingParentCallCommonInspection + * + * @return \noxkiwi\core\Request + */ + public static function getInstance(): static + { + if (! isset(static::$inst)) { + $className = static::getRequestType(); + static::$inst = new $className(); + static::$inst->build(); + } + + return static::$inst; + } + + /** + * Returns the Request type that is used for this Request + * + * @return string + */ + #[Pure] private static function getRequestType(): string + { + if (static::isCli()) { + return CliRequest::class; + } + + return HttpRequest::class; + } + + /** + * returns true if the current php process is being run from a command line interface. + * + * @return bool + */ + public static function isCli(): bool + { + return PHP_SAPI === 'cli'; + } + + /** + * @inheritDoc + */ + public function build(): Request + { + return $this; + } + + /** + * I will return whether the current request is a POST request. + * @return bool + */ + final public static function isPost(): bool + { + return $_SERVER['REQUEST_METHOD'] === WebHelper::METHOD_POST; + } + + /** + * I will return the identifier of this distinct request. + * @return string + */ + final public function getIdentifier(): string + { + if (empty($this->identifier)) { + $id = md5(json_encode($this->get()) . date('Y-m-d H:i:s:u')); + $this->identifier = $id; + } + + return $this->identifier; + } +} diff --git a/backend/class/Request/CliRequest.php b/backend/class/Request/CliRequest.php new file mode 100644 index 0000000..779fba3 --- /dev/null +++ b/backend/class/Request/CliRequest.php @@ -0,0 +1,18 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CliRequest extends Request +{ +} diff --git a/backend/class/Request/HttpRequest.php b/backend/class/Request/HttpRequest.php new file mode 100644 index 0000000..4ca2268 --- /dev/null +++ b/backend/class/Request/HttpRequest.php @@ -0,0 +1,163 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +class HttpRequest extends Request +{ + /** + * Contains data for redirecting the user after finishing the Request + * + * @var string + */ + protected string $redirect; + + /** + * @inheritDoc + */ + protected function __construct(array $data = []) + { + parent::__construct(); + $this->add(filter_input_array(INPUT_SERVER) ?? []); + $this->add(filter_input_array(INPUT_COOKIE) ?? []); + $this->add(filter_input_array(INPUT_GET) ?? []); + $this->add(filter_input_array(INPUT_POST) ?? []); + $decoded = (array)(json_decode(file_get_contents('php://input') ?? '', true) ?? []); + $this->add($decoded); + if (! is_array($decoded)) + { + return; + } + $this->add($data); + } + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return \noxkiwi\core\Request + */ + public function build(): Request + { + parent::build(); + Cookie::getInstance()->start(); + $uri = substr($_SERVER['REQUEST_URI'], 1); + $requestData = $this->getRedirection($uri); + if (! empty($uri) && ! empty($requestData)) { + foreach ($requestData as $key => $value) { + $this->set($key, $value); + } + } else { + if (empty($_GET) && ! empty($uri)) { + $noParams = explode('?', $_SERVER['REQUEST_URI'])[0]; + try { + $params = LinkHelper::decryptLink($noParams); + parse_str($params, $_GET); + } catch (Exception) { + exit(WebHelper::HTTP_BAD_REQUEST); + } + } + static::getInstance()->add($_GET); + $defaultContext = Application::getInstance()->get('defaultcontext'); + $context = static::getInstance()->get(Mvc::CONTEXT, $defaultContext); + static::getInstance()->set(Mvc::CONTEXT, $context); + $defaultView = Application::getInstance()->get('context>' . $context . '>defaultview'); + $view = static::getInstance()->get(Mvc::VIEW, $defaultView); + static::getInstance()->set(Mvc::VIEW, $view); + } + + return $this; + } + + /** + * I may convert the given $readable path into real Request data + * + * @param string $readable + * + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return array + */ + protected function getRedirection(string $readable): array + { + if (! Environment::getInstance()->exists('urlrewrite')) { + return []; + } + try { + $readable = strtok(strtolower($readable), '?'); + + return UrlRewrite::getInstance()->get($readable); + } catch (Exception $exception) { + ErrorHandler::handleException($exception); + } + + return []; + } + + /** + * @deprecated + * + * @param string $rl + * @param string|null $context + * @param string|null $view + * @param string|null $action + */ + public function setRedirect(string $rl, string $context = null, string $view = null, string $action = null): void + { + if (str_contains($rl, '://') || strncmp($rl, '/', 1) === 0) { + $this->redirect = $rl; + + return; + } + $this->redirect = LinkHelper::makeParameters(compact('context', 'view', 'action')); + } + + /** + * I will try to deserialize the body of the POST data to an array. + * + * @deprecated + */ + public function injectJsonData(): void + { + $jsonString = file_get_contents('php://input'); + if (empty($jsonString)) { + return; + } + try { + $data = JsonHelper::decodeStringToArray($jsonString); + if (! is_array($data) || empty($data)) { + return; + } + } catch (InvalidJsonException $exception) { + ErrorHandler::handleException($exception); + + return; + } + $this->add($data); + } +} diff --git a/backend/class/Request/HttpsRequest.php b/backend/class/Request/HttpsRequest.php new file mode 100644 index 0000000..f22dcf1 --- /dev/null +++ b/backend/class/Request/HttpsRequest.php @@ -0,0 +1,16 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class HttpsRequest extends HttpRequest +{ +} diff --git a/backend/class/Response.php b/backend/class/Response.php new file mode 100644 index 0000000..6cc7636 --- /dev/null +++ b/backend/class/Response.php @@ -0,0 +1,131 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +abstract class Response implements ResponseInterface +{ + use DatacontainerTrait; + use LogTrait; + + /** @var string Contains the derived output */ + protected string $output; + /** @var array I contain various frontend resource */ + protected array $resources = []; + /** @var int Contains the status code */ + protected int $statusCode = WebHelper::HTTP_OKAY; + /** @var string Contains the status text */ + protected string $statusText = 'OK'; + private static self $instance; + + /** + * If the single instance does not exist, create it. + * Return the single instance then. + * + * @noinspection PhpMissingParentCallCommonInspection + * + * @return \noxkiwi\core\Response + */ + public static function getInstance(): static + { + if (isset(static::$instance) && static::$instance instanceof self) { + return static::$instance; + } + $className = static::getResponseType(); + $request = Request::getInstance(); + $response = new $className(); + $response->set(Mvc::CONTEXT, $request->get(Mvc::CONTEXT)); + $response->set(Mvc::VIEW, $request->get(Mvc::VIEW)); + $response->set(Mvc::ACTION, $request->get(Mvc::ACTION)); + static::$instance = $response; + + return static::$instance; + } + + /** + * Returns the Request type that is used for this Request + * + * @return string + */ + #[Pure] protected static function getResponseType(): string + { + if (Request::isCli()) { + return CliResponse::class; + } + + return HttpResponse::class; + } + + /** + * Returns the status code of the current Response + * + * @return int + */ + public function getStatuscode(): int + { + return $this->statusCode; + } + + /** + * Helper to set HTTP status codes + * + * @param int $statusCode + * @param string $statusText + * + * @return Response + */ + public function setStatusCode(int $statusCode, string $statusText): Response + { + $this->statusCode = $statusCode; + $this->statusText = $statusText; + + return $this; + } + + /** + * Actually sends output to the Response HTTP Stream + */ + public function pushOutput(): void + { + echo $this->getOutput(); + } + + /** + * Returns the output content of this Response + * + * @return string + */ + public function getOutput(): string + { + return $this->output ?? ''; + } + + /** + * Sets the output of this Response + * + * @param string $output + */ + public function setOutput(string $output): void + { + $this->output = $output; + } +} diff --git a/backend/class/Response/CallbackResponse.php b/backend/class/Response/CallbackResponse.php new file mode 100644 index 0000000..6961d59 --- /dev/null +++ b/backend/class/Response/CallbackResponse.php @@ -0,0 +1,57 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CallbackResponse extends Response +{ + /** @var string[] */ + protected array $jsActions; + + /** + * @inheritDoc + */ + protected function __construct() + { + parent::__construct(); + $this->jsActions = []; + } + + /** + * I will add a notification to the Response + * + * @param string $subject + * @param string $text + * @param string $image + * @param string $sound + */ + public function addNotification(string $subject, string $text, string $image, string $sound): void + { + $this->addJs("rsNotify('$subject', '$text', '$image', '$sound'"); + } + + /** + * I will add a JS body to the Response + * + * @param string $js + */ + public function addJs(string $js): void + { + $jsdo = $this->get('jsdo'); + if ($jsdo === null) { + $jsdo = []; + } + $jsdo[] = $js; + $this->set('jsdo', $jsdo); + } +} diff --git a/backend/class/Response/CliResponse.php b/backend/class/Response/CliResponse.php new file mode 100644 index 0000000..a94d05a --- /dev/null +++ b/backend/class/Response/CliResponse.php @@ -0,0 +1,21 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CliResponse extends Response +{ + public const EXITCODE_OKAY = 0; + public const EXITCODE_ERROR = 127; + public const EXITCODE_FATALERROR = 254; +} diff --git a/backend/class/Response/HttpResponse.php b/backend/class/Response/HttpResponse.php new file mode 100644 index 0000000..f013028 --- /dev/null +++ b/backend/class/Response/HttpResponse.php @@ -0,0 +1,104 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +class HttpResponse extends Response +{ + public const HEADER_ERROR = 'HTTP/1.1 500 Internal Server Error'; + public const HEADER_OK = 'HTTP/1.1 200 OK'; + + /** + * I store the requirement of additional frontend resource in the Response container + * + * @param string $type + * @param string $path + * + * @throws \noxkiwi\core\Exception\InvalidArgumentException + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return bool + */ + public function requireResource(string $type, string $path): bool + { + if (! in_array($type, ['js', 'css'])) { + throw new InvalidArgumentException('EXCEPTION_REQUIRERESOURCE_INVALIDRESOURCETYPE', E_NOTICE, $type); + } + if (strpos('://', $path) && ! Filesystem::getInstance()->fileAvailable(Path::$webRoot . $path)) { + throw new InvalidArgumentException('EXCEPTION_REQUIRERESOURCE_RESOURCENOTFOUND', E_NOTICE, $path); + } + $this->resources[$type][] = $path; + + return true; + } + + /** + * Returns an array of resource that have been requested by the backend + * + * @param string $type + * + * @return array + */ + public function getResources(string $type): array + { + return $this->resources[$type] ?? []; + } + + /** + * Will show a desktop notification on the browser if the Client allowed it. + * + * @see ./www/public/library/templates/shared/javascript/alpha_engine.js :: doCallback($url, callback()); + * + * @param string $subject + * @param string $text + * @param string $image + * @param string $sound + * + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function addNotification(string $subject, string $text, string $image, string $sound): void + { + $file = Path::$webRoot . $image; + if (! Filesystem::getInstance()->fileAvailable($file)) { + $this->logDebug("Cannot send notification, the image $file is not available!"); + + return; + } + $file = Path::$webRoot . $sound; + if (! Filesystem::getInstance()->fileAvailable($file)) { + $this->logDebug("Cannot send notification, the sound $file is not available!"); + + return; + } + $this->addJs("rsNotify('$subject', '$text', '$image', '$sound');"); + } + + /** + * Add a JS resource to the Response template + * + * @param string $js + */ + public function addJs(string $js): void + { + $jsdo = $this->get('jsdo'); + if ($jsdo === null) { + $jsdo = []; + } + $jsdo[] = $js; + $this->set('jsdo', $jsdo); + } +} diff --git a/backend/class/Response/HttpsResponse.php b/backend/class/Response/HttpsResponse.php new file mode 100644 index 0000000..be1d5fb --- /dev/null +++ b/backend/class/Response/HttpsResponse.php @@ -0,0 +1,16 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class HttpsResponse extends HttpResponse +{ +} diff --git a/backend/class/Session.php b/backend/class/Session.php new file mode 100644 index 0000000..97a4d9e --- /dev/null +++ b/backend/class/Session.php @@ -0,0 +1,46 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.0.2 + * @link https://nox.kiwi/ + */ +abstract class Session extends Singleton implements SessionInterface +{ + protected const USE_DRIVER = true; + public const SESSIONKEY = 'phpsessid'; + + /** + * I will construct the Session adding the given $data into the Session. + * + * Note: To CREATE a re-useable Session object, you'll need to call >create as shown in the example. + * @examle + * $session = Session::getInstance(); + * $session->create($data) + * + * @param array $data + */ + protected function __construct(array $data = []) + { + parent::__construct(); + $this->add($data); + } + + /** + * I will return the identifier of this session. + * @return string + */ + public static function getIdentifier(): string + { + return session_id(); + } +} diff --git a/backend/class/Session/CacheSession.php b/backend/class/Session/CacheSession.php new file mode 100644 index 0000000..7cb0af1 --- /dev/null +++ b/backend/class/Session/CacheSession.php @@ -0,0 +1,167 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +final class CacheSession extends Session +{ + /** + * I am the timeout, that is used for the sessions if they are not used (touched) on the cache service. + * + * @var int + */ + private int $timeout = 1200; + + /** + * @inheritDoc + * + * @param array $data + * + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return \noxkiwi\core\Session + */ + public function start(array $data): Session + { + if (isset($data['timeout']) && is_numeric($data['timeout']) && $data['timeout'] > 0) { + $this->timeout = (int)$data['timeout']; + } + Cache::getInstance()->set(self::getCachegroup(), 'SESSION', $data, Cache::DEFAULT_TIMEOUT); + + return $this; + } + + public const CACHE_GROUP = Cache::DEFAULT_PREFIX . 'SESSION_'; + + /** + * I will return a basic cache group name for this class + * + * @throws \noxkiwi\singleton\Exception\SingletonException + * @return string + */ + private static function getCachegroup(): string + { + return self::CACHE_GROUP . Cookie::getInstance()->get(Session::SESSIONKEY); + } + + /** + * @inheritDoc + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function destroy(): void + { + Cache::getInstance()->clearKey(self::getCachegroup(), 'SESSION'); + } + + /** + * @inheritDoc + */ + public function identify(): bool + { + $this->makeData(); + $this->put($this->get()); + + return is_array($this->get()) && count($this->get()) > 2; + } + + /** + * @inheritDoc + */ + public function put(array $data): void + { + $_SESSION = $data; + } + + /** + * I will set the data property of the instance at first + * + * @return void + */ + private function makeData(): void + { + try { + $data = Cache::getInstance()->get(self::getCachegroup(), 'SESSION'); + if (! is_array($data) || empty($data)) { + return; + } + $this->put($data); + Cache::getInstance()->set(self::getCachegroup(), 'SESSION', $this->get(), $this->timeout); + $this->get(); + + return; + } catch (Exception $exception) { + ErrorHandler::handleException($exception); + + return; + } + } + + /** + * @inheritDoc + */ + public function exists(string $key): bool + { + $this->makeData(); + + return $this->get($key) === null; + } + + /** + * @inheritDoc + * + * @param string $key + * @param mixed $data + * + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function set(string $key, mixed $data): void + { + $this->makeData(); + Cache::getInstance()->set(self::getCachegroup(), 'SESSION', $this->get(), $this->timeout); + } + + /** + * @inheritDoc + * + * @throws \noxkiwi\singleton\Exception\SingletonException + */ + public function add(array $data): void + { + foreach ($data as $key => $value) { + $this->set($key, $value); + } + } + + /** + * @inheritDoc + */ + public function remove(string $key): void + { + $data = $this->get(); + unset($data[$key]); + # $this->set($key) + } + + /** + * @inheritDoc + */ + public function get(string $key = null, mixed $default = null): mixed + { + return 0; + } +} diff --git a/backend/class/Session/SessionSession.php b/backend/class/Session/SessionSession.php new file mode 100644 index 0000000..3a32477 --- /dev/null +++ b/backend/class/Session/SessionSession.php @@ -0,0 +1,141 @@ + + * @license http://www.noxkiwi.de/license + * @copyright 2016-2018 noxkiwi + * @version 1.0.0 + * @link http://www.noxkiwi.de/ + */ +final class SessionSession extends Session +{ + /** + * SessionSession constructor. + * + * @param array $data + * + * @throws \noxkiwi\core\Exception\SystemComponentException + * @throws \noxkiwi\core\Exception\SessionException + */ + protected function __construct(array $data = []) + { + if (! extension_loaded('session')) { + throw new SystemComponentException('MISSING_EXTENSION_PHP_SESSION', E_ERROR); + } + if (! session_start()) { + throw new SessionException('UNABLE_TO_START_SESSION', E_ERROR); + } + parent::__construct($data); + } + + /** + * Creates the instance and saves the data object in it + * + * @author Jan Nox + * + * @param array $data + * + * @return Session + */ + public function start(array $data): Session + { + $_SESSION = $data; + + return $this; + } + + /** + * Ends the Session the current user is in + * @author Jan Nox + */ + public function destroy(): void + { + session_destroy(); + unset($_SESSION); + } + + /** + * I will identify the Client and load the Session data to the instance. + *
If identified successfully, I will return TRUE + *
In any other case I will return FALSE + * + * @author Jan Nox + * @return bool + */ + #[Pure] public function identify(): bool + { + return $this->exists('user_username'); + } + + /** + * @inheritDoc + */ + public function add(array $data): void + { + foreach ($data as $key => $value) { + $this->set((string)$key, $value); + } + } + + /** + * @inheritDoc + */ + #[Pure] public function get(string $key = null, mixed $default = null): mixed + { + if (empty($key)) { + return $_SESSION; + } + if (! $this->exists($key)) { + return $default; + } + + return $_SESSION[$key]; + } + + /** + * @inheritDoc + */ + public function set(string $key, mixed $data): void + { + $_SESSION[$key] = $data; + } + + /** + * @inheritDoc + */ + public function remove(string $key): void + { + $_SESSION[$key] = null; + unset($_SESSION[$key]); + } + + /** + * @inheritDoc + */ + public function exists(string $key): bool + { + return array_key_exists($key, $_SESSION); + } + + /** + * @inheritDoc + */ + public function put(array $data): void + { + // TODO: Implement put() method. + } +} diff --git a/backend/class/Traits/DatacontainerTrait.php b/backend/class/Traits/DatacontainerTrait.php new file mode 100644 index 0000000..cc515b0 --- /dev/null +++ b/backend/class/Traits/DatacontainerTrait.php @@ -0,0 +1,137 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2021 noxkiwi + * @version 1.1.0 + * @link https://nox.kiwi/ + */ +trait DatacontainerTrait +{ + /** @var array Contains the data of this instance */ + private array $data = []; + + /** + * Creates the instance and imports the $data object into this instance + * + * @param array|null $data + */ + public function __construct(array $data = null) + { + $this->add($data ?? []); + } + + /** + * @inheritDoc + */ + final public function add(array $data): void + { + foreach ($data as $key => $value) { + if (is_object($key) || is_array($key)) { + continue; + } + $this->set((string)$key, $value); + } + } + + /** + * @inheritDoc + */ + final public function exists(string $key): bool + { + return $this->get($key) !== null; + } + + /** + * @inheritDoc + */ + final public function remove(string $key): void + { + if ($key === '') { + return; + } + if (! $this->exists($key)) { + return; + } + $this->set($key, null); + } + + /** + * @inheritDoc + */ + final public function get(string $key = null, $default = null): mixed + { + if (in_array($key, [null, ''], true)) { + return $this->data; + } + if (! str_contains($key, '>')) { + return $this->data[$key] ?? $default; + } + $myValue = $this->data; + $keyArray = explode('>', $key); + foreach ($keyArray as $myKey) { + if (! isset($myValue[$myKey])) { + return $default; + } + $myValue = $myValue[$myKey]; + } + + return $myValue; + } + + /** + * @inheritDoc + */ + final public function set(string $key, mixed $data): void + { + if ($key === '') { + return; + } + if (str_contains($key, '>')) { + $this->data = $this->setArray($key, $data); + + return; + } + $this->data[$key] = $data; + } + + /** + * I will write down the given $value under the NESTED node identified with the given $key + * + * @param string $key + * @param mixed $value + * + * @return array + * @link http://stackoverflow.com/questions/13359681/how-to-set-a-deep-array-in-php + * + */ + final protected function setArray(string $key, mixed $value): array + { + $current = &$this->data; + $keyElements = explode('>', $key); + foreach ($keyElements as $keyElement) { + $current = &$current[$keyElement]; + } + $current = $value; + + return $this->data; + } + + /** + * @inheritDoc + */ + final public function put(array $data): void + { + $this->data = $data; + } +} diff --git a/backend/class/Traits/ErrorstackTrait.php b/backend/class/Traits/ErrorstackTrait.php new file mode 100644 index 0000000..6d53ea4 --- /dev/null +++ b/backend/class/Traits/ErrorstackTrait.php @@ -0,0 +1,103 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +trait ErrorstackTrait +{ + /** + * I will add a new error with the given information. + * + * @param string $code I am the specific error code. I may be translatable. + * @param mixed $detail I am more information about the error that occured. + * + * @return \noxkiwi\core\ErrorStack + */ + public function addError(string $code, mixed $detail = null): ErrorStack + { + if (! isset($this->errorStack) || ! $this->errorStack instanceof ErrorStack) { + $this->errorStack = new ErrorStack(static::class); + } + + return $this->errorStack->addError($code, $detail); + } + + /** + * I will return the first error that exists in the stack. + * If no errors occurred, I will return null. + * @return \noxkiwi\core\Error|null + */ + public function getFirstError(): ?Error + { + if (! isset($this->errorStack) || ! $this->errorStack instanceof ErrorStack) { + $this->errorStack = new ErrorStack(static::class); + } + + return $this->errorStack->getFirstError(); + } + + /** + * I will return all errors that have been stored in the stack. + * + * @return array + */ + public function getErrors(): array + { + if (! isset($this->errorStack) || ! $this->errorStack instanceof ErrorStack) { + $this->errorStack = new ErrorStack(static::class); + } + + return $this->errorStack->getAll(); + } + + /** + * I will return true if there are no errors in the stack. + * + * @return bool + */ + public function hasErrors(): bool + { + if (! isset($this->errorStack) || ! $this->errorStack instanceof ErrorStack) { + $this->errorStack = new ErrorStack(static::class); + } + + return $this->errorStack->isSuccess(); + } + + /** + * I will remove all errors from the Errorstack instance. + */ + public function clearErrors(): void + { + if (! isset($this->errorStack) || ! $this->errorStack instanceof ErrorStack) { + $this->errorStack = new ErrorStack(static::class); + } + $this->errorStack->reset(); + } + + /** + * Adds a callback for errors. + *
Add the $object and the $function of the object that will be called + * + * @param \object $object + * @param string $function + */ + public function setErrorCallback(object $object, string $function): void + { + if (! isset($this->errorStack) || ! $this->errorStack instanceof ErrorStack) { + $this->errorStack = new ErrorStack(static::class); + } + $this->errorStack->setCallback($object, $function); + } +} diff --git a/backend/class/Traits/LanguageImprovementTrait.php b/backend/class/Traits/LanguageImprovementTrait.php new file mode 100644 index 0000000..a293973 --- /dev/null +++ b/backend/class/Traits/LanguageImprovementTrait.php @@ -0,0 +1,36 @@ + + * @license https://nox.kiwi/license + * @copyright 2019 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +trait LanguageImprovementTrait +{ + /** + * I will simply return the given $value without doing anything about it. + * + * WHY? You ask? + * + *

<<<HTML + *
Now we can use {$this->returnIt(Class::CONSTANTS)} in Heredoc. + *
Now we can use {$this->returnIt(native($functions))} in Heredoc. + *
>>>HTML + *

+ * That's why! + * + * @param mixed $value + * + * @return mixed + */ + final public function returnIt(mixed $value): mixed + { + return $value; + } +} diff --git a/backend/class/Traits/TranslationTrait.php b/backend/class/Traits/TranslationTrait.php new file mode 100644 index 0000000..24365a6 --- /dev/null +++ b/backend/class/Traits/TranslationTrait.php @@ -0,0 +1,41 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @link https://nox.kiwi/ + */ +trait TranslationTrait +{ + /** + * Transllates the given key. If there's no PERIOD in the key, the function will use APP.$CONTEXT_$VIEW as prefix + * automatically. I will suppress every Exception. + * + * @param string $key + * @param array|null $context + * + * @return string + */ + public function translate(string $key, array $context = null): string + { + $context ??= []; + $key = strtoupper($key); + try { + return Translator::getInstance()->translate($key, $context); + } catch (Exception $exception) { + ErrorHandler::handleException($exception); + + return ''; + } + } +} diff --git a/backend/class/Validator/Structure/Config/AppValidator.php b/backend/class/Validator/Structure/Config/AppValidator.php new file mode 100644 index 0000000..e91b8bb --- /dev/null +++ b/backend/class/Validator/Structure/Config/AppValidator.php @@ -0,0 +1,37 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @since 1.0.0 Stable (JG 2018-07-29) + * @link https://nox.kiwi/ + */ +class AppValidator extends ConfigValidator +{ + /** + * @inheritDoc + */ + protected function __construct(array $options = []) + { + $this->setOptions( + [ + static::OPTION_STRUCTURE => [ + Mvc::CONTEXT => ContextValidator::class . '[]', + 'defaultcontext' => SecureNameValidator::class, + 'defaulttemplate' => SecureNameValidator::class + ] + ] + ); + parent::__construct($options); + } +} diff --git a/backend/class/Validator/Structure/Config/ContextValidator.php b/backend/class/Validator/Structure/Config/ContextValidator.php new file mode 100644 index 0000000..660b15d --- /dev/null +++ b/backend/class/Validator/Structure/Config/ContextValidator.php @@ -0,0 +1,25 @@ + + * @license https://nox.kiwi/license + * @copyright 2016 - 2018 noxkiwi + * @version 1.0.0 + * @since 1.0.0 Stable (JG 2018-07-29) + * @link https://nox.kiwi/ + */ +class ContextValidator extends ConfigValidator +{ + /** + * @inheritDoc + */ + protected array $structureDesign = ['defaultview' => SecureNameValidator::class, Mvc::VIEW => null]; +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..131c37e --- /dev/null +++ b/composer.json @@ -0,0 +1,61 @@ +{ + "name": "noxkiwi/core", + "description": "I am the core framework for all applications from noxkiwi. My collection of functionality is quite big, my app class is a simple, yet usable and flexible toolbox for all kinds of tasks.", + "version": "1.0.0", + "type": "package", + "license": "proprietary", + "keywords": [ + "core", + "main", + "framework" + ], + "homepage": "https://nox.kiwi/", + "time": "2018-05-29", + "authors": [ + { + "name": "Jan Nox", + "email": "jan.nox@pm.me", + "homepage": "https://nox.kiwi", + "role": "Lead Developer" + } + ], + "require": { + "php": ">=8.0.0", + "noxkiwi/validator": "*", + "noxkiwi/hook": "*", + "noxkiwi/log": "*", + "noxkiwi/singleton": "*", + "noxkiwi/cache": "*", + "noxkiwi/stack": "*", + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-fileinfo": "*", + "ext-sockets": "*", + "ext-openssl": "*", + "ext-bcmath": "*", + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7" + }, + "support": { + "email": "jan.nox@pm.me", + "issues": "https://nox.kiwi/issues", + "forum": "https://nox.kiwi/forum", + "wiki": "https://nox.kiwi/wiki", + "docs": "https://noxkiwi.atlassian.net/wiki", + "source": "https://waetjuzd747uthrv83pe7snpnrjca46c9zvp4dq8gvrvjyxnns7caaxy7gnefaay.nox.kiwi:4430/projects/RS/repos/Core/browse" + }, + "autoload": { + "psr-4": { + "noxkiwi\\core\\": "backend/class/" + } + }, + "extra": { + "packages-to-create": [ + "gate", + "auth" + ] + } +} diff --git a/config/context/bucketbrowser.json b/config/context/bucketbrowser.json new file mode 100644 index 0000000..3fbb23b --- /dev/null +++ b/config/context/bucketbrowser.json @@ -0,0 +1,13 @@ +{ + "defaultview": "list", + "view": { + "list": {}, + "fildedelete": {}, + "fileupload": {}, + "filedownload": {}, + "delete": {}, + "rename": {}, + "move": {}, + "copy": {} + } +} diff --git a/config/context/crud.json b/config/context/crud.json new file mode 100644 index 0000000..508e6b6 --- /dev/null +++ b/config/context/crud.json @@ -0,0 +1,13 @@ +{ + "defaultview": "crudlist", + "view": { + "crudlist": {}, + "crudcreate": {}, + "crudedit": {}, + "crudshow": {}, + "crudduplicate": {}, + "cruddelete": {}, + "bulkedit": {}, + "bulkdelete": {} + } +} diff --git a/config/context/kit.json b/config/context/kit.json new file mode 100644 index 0000000..8c99f4b --- /dev/null +++ b/config/context/kit.json @@ -0,0 +1,7 @@ +{ + "defaultview": "listkeys", + "view": { + "listkeys": {}, + "key": {} + } +} diff --git a/config/context/resource.json b/config/context/resource.json new file mode 100644 index 0000000..b8884d0 --- /dev/null +++ b/config/context/resource.json @@ -0,0 +1,8 @@ +{ + "defaultview": "file", + "view": { + "file": { + "public": true + } + } +} \ No newline at end of file diff --git a/frontend/page/error.php b/frontend/page/error.php new file mode 100644 index 0000000..8d96c7b --- /dev/null +++ b/frontend/page/error.php @@ -0,0 +1,89 @@ + + + + + + + + Uups! + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+
+
+

+

500 Interner Serverfehler

+

+ Die Plattform (auf der die Webseite läuft) hat einen unerwarteten Fehler festgestellt + und hat die Verarbeitung abgebrochen, um Schlimmeres zu vermeiden. +

+
+
+
+
+
+
+
+ + + + +
+
+ +
+
+ + + + + + diff --git a/frontend/page/errorinfo.php b/frontend/page/errorinfo.php new file mode 100644 index 0000000..81af8f1 --- /dev/null +++ b/frontend/page/errorinfo.php @@ -0,0 +1,316 @@ +getLevel(); +} +switch ($level) { + case E_WARNING: + $class = 'warning'; + $errName = '❌'; + break; + case E_NOTICE: + $class = 'info'; + $errName = '⚠️'; + break; + default: + $class = 'danger'; + $errName = '💀'; + break; +} + +use JetBrains\PhpStorm\Pure; +use noxkiwi\cache\Cache; +use noxkiwi\cache\Observer\CacheObserver; +use noxkiwi\core\Helper\WebHelper; +use noxkiwi\database\Database; +use noxkiwi\log\Log\CliLog; +use function print_r; +use const E_ERROR; +use const E_NOTICE; +use const E_WARNING; + +try { + $requestText = print_r( + Request::getInstance()->get(), + true + ); + $responseText = print_r( + Response::getInstance()->get(), + true + ); + $environment = Environment::getInstance(); + $appText = print_r( + Application::getInstance()->get(), + true + ); + $cHit = CacheObserver::$countHit; + $cSet = CacheObserver::$countSet; + $cGet = CacheObserver::$countGet; + $cMiss = CacheObserver::$countMiss; + if ($isCli) { + $a = CliLog::getInstance(); + $a->alert($data::class); + $a->alert($data->getMessage()); + $a->error(print_r($data, true)); + exit(WebHelper::HTTP_SERVER_ERROR); + } + $rt = ''; + if ($data instanceof Exception) { + $rt = print_r($data->getInfo(), true); + } + $errText = print_r(ErrorStack::getErrorStacks(), true); + $className = $data::class; + $ipaddress = WebHelper::getClientIp(); + $admins = (array)$environment->get('errorhandler>admins', []); + /** + * I will create a tab on the error page. + * + * @param string $id + * @param string $content + * + * @return \noxkiwi\core\Tab + */ + #[Pure] function makeTab(string $id, string $content): Tab + { + $tab = new Tab(); + $tab->domId = $id; + $tab->content = $content; + $tab->title = $id; + + return $tab; + } + + /** + * Class Tab + * + * @package noxkiwi\core + * + * @author Jan Nox + * + * @copyright 2021 nox.kiwi + * @version 1.0.0 + */ + class Tab + { + public string $domId; + public string $title; + public string $content; + } + + /** + * Class TabArea + * + * @package noxkiwi\core + * + * @author Jan Nox + * + * @copyright 2021 nox.kiwi + * @version 1.0.0 + */ + class TabArea + { + /** @var \noxkiwi\core\Tab[] */ + public array $tabs = []; + + /** + * I will add a new Tab to the TabArea. + * + * @param \noxkiwi\core\Tab $tab + */ + public function addTab(Tab $tab): void + { + $this->tabs[] = $tab; + } + + /** + * I will output the tabs. + * + * @return string + */ + public function output(): string + { + $tabHeader = ''; + $tabContent = ''; + $active = 'show active'; + foreach ($this->tabs ?? [] as $tab) { + $tabHeader .= << + + +HTML; + $tabContent .= << + $tab->content + +HTML; + $active = ''; + } + + return << + +
+ $tabContent +
+ +HTML; + } + } + + $tabArea = new TabArea(); + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>stacktrace>show', false) === true) { + $stackTrace = ErrorHandler::getStack($data); + $id = 'main'; + $fc = ErrorHandler::getSurround($data->getFile(), $data->getLine()); + $tabContent = << +

+ +

+
+
+ + + + + + + + + + + + + + + +
File{$data->getFile()}
Line{$data->getLine()}
Surrounding
$fc
+
+
+ $stackTrace + +HTML; + $tabArea->addTab(makeTab('Stacktrace', $tabContent)); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>request>show', false) === true) { + $tabContent = <<$requestText +HTML; + $tabArea->addTab(makeTab('Request', $tabContent)); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>response>show', false) === true) { + $tabContent = <<$responseText +HTML; + $tabArea->addTab(makeTab('Response', $tabContent)); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>app>show', false) === true) { + $tabContent = <<$appText +HTML; + $tabArea->addTab(makeTab('Application', $tabContent)); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>cache>show', false) === true) { + $cacheInfo = print_r( + Cache::getInstance()->getAllKeys(), + true + ); + $cacText = << + + + Hit + $cHit + + + Set + $cSet + + + Get + $cGet + + + Miss + $cMiss + + + +
$cacheInfo
+ HTML; + $tabContent = <<$cacText +HTML; + $tabArea->tabs [] = makeTab('Cache', $tabContent); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>errorstack>show', false) === true) { + $tabContent = <<$errText +HTML; + $tabArea->tabs [] = makeTab('Errors', $tabContent); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>queries>show', false) === true) { + $qryText = print_r(Database::$queries, true); + $tabContent = <<$qryText +HTML; + $tabArea->tabs [] = makeTab('Queries', $tabContent); + } + if (in_array($ipaddress, $admins, true) || $environment->get('errorhandler>environment>show', false) === true) { + $envText = print_r($environment->get(), true); + $tabContent = <<$envText +HTML; + $tabArea->addTab(makeTab('Environment', $tabContent)); + } + $tab = $tabArea->output(); + $content = << + + +$tab +HTML; + ?> + + + + + + + + + + + + +
+ +
+ + + + + + + + + + Maintenance Mode + + + + + + + + + + + +
+

+

💀

+

This application went into Maintenance Mode at because of

+ +
+ "Kicking The Bucket" by Vikram Madan +
+ + diff --git a/frontend/page/notallowed.php b/frontend/page/notallowed.php new file mode 100644 index 0000000..2137582 --- /dev/null +++ b/frontend/page/notallowed.php @@ -0,0 +1,88 @@ + + + + + + + + Uups! + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+
+
+

Access denied

+

+ This page should be hidden from you. +
Please log in first. +

+
+
+
+
+
+
+
+ + + + +
+
+ +
+
+ + + + + + diff --git a/frontend/page/notfound.php b/frontend/page/notfound.php new file mode 100644 index 0000000..e261c6f --- /dev/null +++ b/frontend/page/notfound.php @@ -0,0 +1,89 @@ + + + + + + + + Uups! + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+
+
+

+

404 - Nicht gefunden

+

+ Die angeforderte Seite wurde leider nicht gefunden =( +

+
+
+
+
+
+
+
+ + + + +
+
+ +
+
+ + + + + + diff --git a/frontend/template/blank/template.php b/frontend/template/blank/template.php new file mode 100644 index 0000000..72ad40b --- /dev/null +++ b/frontend/template/blank/template.php @@ -0,0 +1,4 @@ +get('content'); diff --git a/frontend/template/json/template.php b/frontend/template/json/template.php new file mode 100644 index 0000000..5911841 --- /dev/null +++ b/frontend/template/json/template.php @@ -0,0 +1,9 @@ +get() +); diff --git a/frontend/view/form/form.php b/frontend/view/form/form.php new file mode 100644 index 0000000..61cc7d0 --- /dev/null +++ b/frontend/view/form/form.php @@ -0,0 +1,4 @@ +get('form'); diff --git a/frontend/view/form/redirect.php b/frontend/view/form/redirect.php new file mode 100644 index 0000000..73e9005 --- /dev/null +++ b/frontend/view/form/redirect.php @@ -0,0 +1,17 @@ + + + diff --git a/frontend/view/form/success.php b/frontend/view/form/success.php new file mode 100644 index 0000000..bcb464a --- /dev/null +++ b/frontend/view/form/success.php @@ -0,0 +1,9 @@ + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..f6dd54a --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,7 @@ + + + + test/phpunit/ValidatorTest.php + + + diff --git a/translation/de-DE/api_jossis.json b/translation/de-DE/api_jossis.json new file mode 100644 index 0000000..6ca48b9 --- /dev/null +++ b/translation/de-DE/api_jossis.json @@ -0,0 +1,7 @@ +{ + "APP_NOT_ALLOWED_FOR_YOU": "Die angeforderte App ist nicht für Sie freigegeben", + "REQUESTED_USER_NOT_FOUND": "Die eingegebenen Benutzerdaten existieren nicht", + "USER_NOT_ACTIVATED": "Der angeforderte Benutzer ist gesperrt.
Wenden Sie sich bitte telefonisch an uns.", + "UNKNOWN_EXCEPTION": "Unbekannter Fehler bei der Anmeldung
Bitte wenden Sie sich telefonisch an uns.", + "AUTH_FAILED": "Diese App konnte sich nicht zum Loginsystem verbinden. Anmeldefehler" +} diff --git a/translation/de-DE/button.json b/translation/de-DE/button.json new file mode 100644 index 0000000..424cbd5 --- /dev/null +++ b/translation/de-DE/button.json @@ -0,0 +1,34 @@ +{ + "BTN_PROCEED": "Fortfahren", + "BTN_CANCEL": "Abbrechen", + "BTN_ORDER": "Aufträge", + "BTN_PASSWORDRESET": "Passwort zurücksetzen", + "BTN_SENDCONTRACT": "Vertrag senden", + "BTN_CREATEACCOUNT": "Zugangsdaten senden", + "BTN_ACTIVATE": "Aktivieren", + "BTN_DECLINE": "Ablehnen", + "BTN_DRAFTS": "Entwürfe", + "BTN_POSITIONS": "Positionen", + "BTN_INVOICE": "Rechnungen", + "BTN_PUSH": "Push-Nachricht senden", + "BTN_DISABLE": "Deaktivieren", + "BTN_TRACK": "Verfolgen", + "BTN_ACCOUNT": "Konto verwalten", + "BTN_DOCUMENT": "Dokumente", + "BTN_INFO": "Informationen", + "BTN_CREATE": "Neu...", + "BTN_EDIT": "Bearbeiten", + "BTN_DELETE": "Entfernen", + "BTN_FILTER": "Filtern", + "BTN_EXPORT": "Exportieren", + "BTN_DOWNLOAD": "Herunterladen", + "BTN_CHARGEBACK": "Gutschrift", + "BTN_PAY": "Bezahlen", + "BTN_INVOICEPOSITIONS": "Positionen bearbeiten", + "BTN_INVOICESEND": "Abschließen", + "BTN_UAC": "Benutzer", + "BTN_FILE": "Dateien", + "BTN_SEARCH": "Suchen", + "BTN_OPTIONS": "Optionen", + "BTN_CRUD_SHOW": "Anzeigen" +} diff --git a/translation/de-DE/crud.json b/translation/de-DE/crud.json new file mode 100644 index 0000000..5932a9d --- /dev/null +++ b/translation/de-DE/crud.json @@ -0,0 +1,13 @@ +{ + "SAVE_ERROR": "Korrigieren Sie bitte die markierten Felder", + "INCOMPLETE": "Füllen Sie bitte alle Felder aus", + "PLEASE_SELECT": "Bitte auswählen", + "REQUIREDFIELDS": "Mit (*) markierte Felder müssen ausgefüllt werden.", + "DELETE_BTN_PROCEED": "Ja, löschen", + "DELETE_BTN_ABORT": "Nicht löschen", + "DELETE_TEXT_REALLY": "Soll das Objekt wirklich gelöscht werden?", + "DELETE_TEXT_INHERITANCE": "Abhängige Objekte werden ebenfalls gelöscht!", + "DELETE_HEADER_CONFIRM": "Löschen bestätigen", + "ENTRY_UPDATE": "Der Eintrag wurde erfolgreich aktualisiert", + "ENTRY_CREATE": "Der Eintrag wurde erfolgreich hinzugefügt" +} diff --git a/translation/de-DE/datetime.json b/translation/de-DE/datetime.json new file mode 100644 index 0000000..28a95aa --- /dev/null +++ b/translation/de-DE/datetime.json @@ -0,0 +1,43 @@ +{ + "MONTH01": "Januar", + "MONTH02": "Februar", + "MONTH03": "März", + "MONTH04": "April", + "MONTH05": "Mai", + "MONTH06": "Juni", + "MONTH07": "Juli", + "MONTH08": "August", + "MONTH09": "September", + "MONTH10": "Oktober", + "MONTH11": "November", + "MONTH12": "Dezember", + "MONTH01ABBR": "Jan", + "MONTH02ABBR": "Feb", + "MONTH03ABBR": "Mrz", + "MONTH04ABBR": "Apr", + "MONTH05ABBR": "Mai", + "MONTH06ABBR": "Jun", + "MONTH07ABBR": "Jul", + "MONTH08ABBR": "Aug", + "MONTH09ABBR": "Sep", + "MONTH10ABBR": "Okt", + "MONTH11ABBR": "Nov", + "MONTH12ABBR": "Dez", + "DAYNAME1": "Montag", + "DAYNAME2": "Dienstag", + "DAYNAME3": "Mittwoch", + "DAYNAME4": "Donnerstag", + "DAYNAME5": "Freitag", + "DAYNAME6": "Samstag", + "DAYNAME7": "Sonntag", + "DAYABBR1": "Mo", + "DAYABBR2": "Di", + "DAYABBR3": "Mi", + "DAYABBR4": "Do", + "DAYABBR5": "Fr", + "DAYABBR6": "Sa", + "DAYABBR7": "So", + "FORMAT_DATE_TIME": "d.m.Y, H:i:s", + "FORMAT_DATE": "d.m.Y" +} + diff --git a/translation/de-DE/rsssis.json b/translation/de-DE/rsssis.json new file mode 100644 index 0000000..6ca48b9 --- /dev/null +++ b/translation/de-DE/rsssis.json @@ -0,0 +1,7 @@ +{ + "APP_NOT_ALLOWED_FOR_YOU": "Die angeforderte App ist nicht für Sie freigegeben", + "REQUESTED_USER_NOT_FOUND": "Die eingegebenen Benutzerdaten existieren nicht", + "USER_NOT_ACTIVATED": "Der angeforderte Benutzer ist gesperrt.
Wenden Sie sich bitte telefonisch an uns.", + "UNKNOWN_EXCEPTION": "Unbekannter Fehler bei der Anmeldung
Bitte wenden Sie sich telefonisch an uns.", + "AUTH_FAILED": "Diese App konnte sich nicht zum Loginsystem verbinden. Anmeldefehler" +} diff --git a/translation/de-DE/validation.json b/translation/de-DE/validation.json new file mode 100644 index 0000000..e09d2fa --- /dev/null +++ b/translation/de-DE/validation.json @@ -0,0 +1,28 @@ +{ + "SAVE_ERROR": "Korrigieren Sie bitte die markierten Felder.", + "FIELD_NOT_SET": "Das Feld darf nicht leer sein", + "STRING_TOO_SHORT": "Der Inhalt ist zu kurz", + "STRING_TOO_LONG": "Der Inhalt ist zu lang", + "STRING_CONTAINS_INVALID_CHARACTERS": "Der Wert enthält ungültige Zeichen", + "IBAN_COUNTRY_NOT_FOUND": "Der Ländercode ist nicht gültig", + "IBAN_CHECKSUM_FAILED": "Die Prüfziffer ist falsch", + "IBAN_LENGH_NOT_MATCHING_COUNTRY": "Die Länge der IBAN stimmt nicht", + "AT_NOT_FOUND": "Das @-Zeichen fehlt", + "VALUE_IS_NULL": "Der Wert ist ungültig", + "FIELD_IS_EMPTY": "Dieses Feld darf nicht leer sein", + "FIELD_IS_REQUIRED": "Dieses Feld darf nicht leer sein", + "FIELD_DUPLICATE": "Duplikat gefunden", + "EMAIL_AT_NOT_FOUND": "Das @-Zeichen fehlt", + "VALUE_NOT_A_NUMBER": "Bitte nur Zahlen eingeben", + "MAIL_HOST_BLOCKED": "Dieser Mail-Anbieter wird nicht akzeptiert.", + "REQUIREDFIELDS": "Markierte Felder bitte ausfüllen", + "EMAIL_DOMAIN_BLOCKED": "Dieser Mail-Anbieter wird nicht akzeptiert.", + "EMAIL_DOMAIN_INVALID": "Die angegebene Domain wurde nicht gefunden", + "PASSWORD_UPPERCASE_CHARACTER_NOT_FOUND": "Bitte auch Großbuchstaben verwenden", + "PASSWORD_LOWERCASE_CHARACTER_NOT_FOUND": "Bitte auch Kleinbuchstaben benutzen", + "PASSWORD_NUMERIC_CHARACTER_NOT_FOUND": "Bitte auch eine Ziffer verwenden", + "PASSWORD_SPECIAL_CHARACTER_NOT_FOUND": "Bitte auch ein Sonderzeichen verwenden", + "VALUE_NOT_A_STRING": "Der Wert ist ungültig", + "NO_PERIOD_FOUND": "Der Punkt fehlt", + "DOMAIN_NOT_RESOLVED": "Die Domain konnte nicht gefunden werden" +}