Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex 2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions

214675
Changelog.xml Normal file

File diff suppressed because it is too large Load Diff

8
INSTALL Normal file
View File

@ -0,0 +1,8 @@
1. aktuelle .tar.gz entpacken
2. per Browser auf auf Verzeichnis gehen
3. setup durchfuehren (trotz Hinweis der Rechte (diese muessen passen) weiter klicken)
4. Der Webserver-Pfad sollte dringend auf www gesetzte werden, so
dass die Firmen kritischen Dateien auf gar keinen Fall durch eine fehlerhafte
.htaccess Datei jemals online stehen (es geht primaer um den Ordner userdata und conf).

10
INSTALL.md Normal file
View File

@ -0,0 +1,10 @@
# Xentral Installation
1. aktuelle .tar.gz entpacken in einem Ordner
2. per Browser auf auf Verzeichnis gehen
3. setup durchfuehren (trotz Hinweis der Rechte (diese muessen passen) weiter klicken)
4. Der Webserver-Pfad sollte dringend auf www gesetzte werden, sodass die
Firmen kritischen Dateien auf keinen Fall durch eine fehlerhafte .htaccess
Datei jemals online stehen (es geht primaer um den Ordner userdata und conf).

209
LICENSE Normal file
View File

@ -0,0 +1,209 @@
Diese Software ist urheberrechtlich geschützt!
Copyright by Xentral ERP Software GmbH (ehemals WaWision GmbH bzw. embedded projects GmbH)
Diese Software ist urheberrechtlich geschützt. Alle aus dem Urheberrecht resultierenden
Rechte stehen dem Lizenzgeber zu. Das Urheberrecht umfaßt insbesondere den
Programmcode, die Dokumentation, das Erscheinungsbild, die Struktur und Organisation
der Programmdateien, den Programmnamen, Logos und andere Darstellungsformen
innerhalb der Software.
Der Lizenznehmer erhält nur das individuelle Nutzungsrecht an der Software. Ein
Erwerb von Rechten an der Software selbst ist damit nicht verbunden. Der Lizenzgeber
behält sich alle Veröffentlichungs-, Vervielfältigungs-, Bearbeitungs- und Verwertungsrechte
an der Software vor.
Diese Software enthält folgenden Drittkomponenten unter den folgenden Lizenzen:
<table class="mkTable">
<tr><th>Komponente</th><th>Lizenz</th><th>Quellcode Download<br>(soweit anwendbar)</th></tr>
<tr valign="top">
<td>phpmailer Version 2.1 (Wed, June 04 2008)</td>
<td>[PHPMAILER]</td>
<td><a href="">http://phpmailer.codeworxtech.com</a></td>
</tr>
</table>
Some icons used in this software are property of Webalys LLC (https://www.webalys.com) and can be used only in the context of this open source project.
phpmailer
GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999
http://phpmailer.codeworxtech.com/
simple_dom_html
Licensed under The MIT License
http://sourceforge.net/projects/simplehtmldom/
full calendar 1.5.3
MIT-LICENSE.txt and GPL-LICENSE.txt respectively
WaWision choose MIT license
http://arshaw.com/fullcalendar/
jqtouch-1.0-beta-2-r109.zip
http://code.google.com/p/jqtouch/
The MIT License
jQuery UI CSS Framework 1.8.6
Dual licensed under the MIT or GPL Version 2 licenses
WaWision choose MIT license
http://jquery.org/license
FPDF
FPDF is released under a permissive license: there is no usage restriction. You may embed it freely in your application (commercial or not), with or without modifications.
http://www.fpdf.org/
Math
http://www.gnu.org/licenses/lgpl.txt
http://pear.php.net/package/Math_BigInteger
phpseclib
http://www.gnu.org/licenses/lgpl.txt
http://phpseclib.sourceforge.net
PHP_Compat /
http://www.gnu.org/licenses/lgpl.html
http://php.net/
Spreadsheet_Excel_Reader
http://www.php.net/license/3_0.txt PHP License 3.0
http://www.roxburgh.me.uk
Crypt
http://www.gnu.org/licenses/lgpl.txt
http://phpseclib.sourceforge.net
pretty autocomplete
COPYING for license information (LGPL)
http://www.horde.org/
jquery.mobile.min.js
The MIT License
http://jquerymobile.com/
colorpicker
COPYING for license information (LGPL).
Original Sphere Plugin v0.1/v0.3, Design/Programming by Ulyses, (c) 2007 ColorJack.com, IE fixes by Hamish.
grider.js
http://www.opensource.org/licenses/mit-license.php
Boris Barroso Camberos
tinymce
GNU LESSER GENERAL PUBLIC LICENSE
moxiecode.com
Flot
http://code.google.com/p/flot/
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Klarna
https://github.com/klarna/php-xmlrpc
The Klarna XMLRPC PHP SDK is released under Apache License, Version 2.0
Klarna AB (publ). Headquarters: Stockholm
jQuery tagEditor Plugin
MIT License © Pixabay.com
https://github.com/Pixabay/jQuery-tagEditor
ColorExtractor
MIT License © Mathieu Lechat
https://github.com/thephpleague/color-extractor
HTML Purifier
GNU LESSER GENERAL PUBLIC LICENSE
https://travis-ci.org/ezyang/htmlpurifier
league/fractal 0.17.0
MIT License
https://github.com/thephpleague/fractal
nikic/fast-route v1.3.0
BSD-3-Clause
https://github.com/nikic/FastRoute
rakit/validation
MIT License
https://github.com/rakit/validation
aura/sqlquery
BSD-2-Clause
https://github.com/auraphp/Aura.SqlQuery
raml2html
MIT License
https://github.com/raml2html/raml2html
raml2html werk theme
MIT License
https://github.com/silicann/raml2html-werk-theme
FPDF Makefont + TTFParser
Permission is hereby granted, free of charge, to any person obtaining a copy of this software to use, copy, modify, distribute, sublicense, and/or sell copies of the software, and to permit persons to whom the software is furnished to do so.
https://github.com/Setasign/FPDF
Nickersoft/push.js
MIT License © Tyler Nickerson
https://github.com/Nickersoft/push.js
sabre/dav
BSD-3-Clause
https://github.com/sabre-io/dav
needim/noty - A jQuery Notification Plugin
MIT License
https://github.com/needim/noty
swiss-payment-slip/swiss-payment-slip-fpdf
MIT License
https://github.com/ravage84/SwissPaymentSlipFpdf
League\Flysystem
MIT License
https://github.com/thephpleague/flysystem
hammerjs/hammer.js
MIT License © Jorik Tangelder (Eight Media)
https://github.com/hammerjs/hammer.js/
haltu/muuri
MIT License © Haltu Oy
https://github.com/haltu/muuri
FitText.js
WTFPL License © Dave Rupert
https://github.com/davatron5000/FitText.js
Smarty template engine
LGPL-3.0 License
https://github.com/smarty-php/smarty
TCPDF
GNU-LGPL v3 © Nicola Asuni - Tecnick.com
https://tcpdf.org
Guzzle, PHP HTTP client
MIT License
https://github.com/guzzle/guzzle
League/OAuth1-Client
MIT License
https://github.com/thephpleague/oauth1-client
Etsy Provider for League/OAuth1-Client
MIT License © Y0lk <gabriel@inkrebit.com>
https://github.com/Y0lk/oauth1-etsy
Lexend Deca Font
SIL Open Font License 1.1 © The Lexend Project Authors
https://github.com/ThomasJockin/lexend
Roboto Font
Apache License, Version 2.0 © Google Inc.
https://github.com/google/roboto
HOTP/TOTP Token Generator
MIT License
https://github.com/lfkeitel/php-totp
phpseclib/phpseclib
MIT License
https://github.com/phpseclib/phpseclib
Laminas/Laminas-mail
BSD-3-Clause
https://github.com/laminas/laminas-mail
Verwenden Sie die Software nur, wenn Sie eine gültige Lizenz erworben haben!
Kontakt: Xentral ERP Software GmbH
Fuggerstraße 11
D-86150 Augsburg
Telefon 0821 268 41 0 41
Telefax 0821 268 41 0 42
E-Mail kontakt@xentral.biz

709
LICENSE.md Normal file
View File

@ -0,0 +1,709 @@
GNU Affero General Public License
=================================
_Version 3, 19 November 2007_
_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
## Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: **(1)** assert copyright on the software, and **(2)** offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions
“This License” refers to version 3 of the GNU Affero General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this
License. Each licensee is addressed as “you”. “Licensees” and
“recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a “modified version” of the
earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based
on the Program.
To “propagate” a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices”
to the extent that it includes a convenient and prominently visible
feature that **(1)** displays an appropriate copyright notice, and **(2)**
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code
The “source code” for a work means the preferred form of the work
for making modifications to it. “Object code” means any non-source
form of a work.
A “Standard Interface” means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other
than the work as a whole, that **(a)** is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and **(b)** serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
“Major Component”, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
### 2. Basic Permissions
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
### 4. Conveying Verbatim Copies
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
* **a)** The work must carry prominent notices stating that you modified
it, and giving a relevant date.
* **b)** The work must carry prominent notices stating that it is
released under this License and any conditions added under section 7.
This requirement modifies the requirement in section 4 to
“keep intact all notices”.
* **c)** You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
* **d)** If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
“aggregate” if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
* **a)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
* **b)** Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either **(1)** a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or **(2)** access to copy the
Corresponding Source from a network server at no charge.
* **c)** Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
* **d)** Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
* **e)** Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A “User Product” is either **(1)** a “consumer product”, which means any
tangible personal property which is normally used for personal, family,
or household purposes, or **(2)** anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, “normally used” refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
“Installation Information” for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms
“Additional permissions” are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
* **a)** Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
* **b)** Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
* **c)** Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
* **d)** Limiting the use for publicity purposes of names of licensors or
authors of the material; or
* **e)** Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
* **f)** Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered “further
restrictions” within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
### 8. Termination
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated **(a)**
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and **(b)** permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents
A “contributor” is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, “control” includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To “grant” such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either **(1)** cause the Corresponding Source to be so
available, or **(2)** arrange to deprive yourself of the benefit of the
patent license for this particular work, or **(3)** arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. “Knowingly relying” means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is “discriminatory” if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license **(a)** in connection with copies of the covered work
conveyed by you (or copies made from those copies), or **(b)** primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
### 13. Remote Network Interaction; Use with the GNU General Public License
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
### 14. Revised Versions of this License
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License “or any later version” applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
### 16. Limitation of Liability
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
_END OF TERMS AND CONDITIONS_
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a “Source” link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a “copyright disclaimer” for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
&lt;<http://www.gnu.org/licenses/>&gt;.
## Plugins/Bibliotheken
phpmailer
GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999
http://phpmailer.codeworxtech.com/
simple_dom_html
Licensed under The MIT License
http://sourceforge.net/projects/simplehtmldom/
full calendar 1.5.3
MIT-LICENSE.txt and GPL-LICENSE.txt respectively
http://arshaw.com/fullcalendar/
jqtouch-1.0-beta-2-r109.zip
http://code.google.com/p/jqtouch/
The MIT License
jQuery UI CSS Framework 1.8.6
Dual licensed under the MIT or GPL Version 2 licenses
http://jquery.org/license
FPDF
FPDF is released under a permissive license: there is no usage restriction. You may embed it freely in your application (commercial or not), with or without modifications.
http://www.fpdf.org/
Math
http://www.gnu.org/licenses/lgpl.txt
http://pear.php.net/package/Math_BigInteger
phpseclib
http://www.gnu.org/licenses/lgpl.txt
http://phpseclib.sourceforge.net
PHP_Compat /
http://www.gnu.org/licenses/lgpl.html
http://php.net/
Spreadsheet_Excel_Reader
http://www.php.net/license/3_0.txt PHP License 3.0
http://www.roxburgh.me.uk
BarcodePHP
http://www.barcodephp.com
On this page, you can download free software for non-commercial use!!!
Crypt
http://www.gnu.org/licenses/lgpl.txt
http://phpseclib.sourceforge.net
pretty autocomplete
COPYING for license information (LGPL)
http://www.horde.org/
jquery.mobile.min.js
The MIT License
http://jquerymobile.com/
colorpicker
COPYING for license information (LGPL).
Original Sphere Plugin v0.1/v0.3, Design/Programming by Ulyses, (c) 2007 ColorJack.com, IE fixes by Hamish.
grider.js
http://www.opensource.org/licenses/mit-license.php
Boris Barroso Camberos
tinymce
GNU LESSER GENERAL PUBLIC LICENSE
moxiecode.com
Flot
http://code.google.com/p/flot/
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

238
LICENSE_LIST Normal file
View File

@ -0,0 +1,238 @@
Diese Software ist urheberrechtlich geschützt!
Copyright by WaWision GmbH.
Diese Software ist urheberrechtlich geschützt. Alle aus dem Urheberrecht resultierenden
Rechte stehen dem Lizenzgeber zu. Das Urheberrecht umfaßt insbesondere den
Programmcode, die Dokumentation, das Erscheinungsbild, die Struktur und Organisation
der Programmdateien, den Programmnamen, Logos und andere Darstellungsformen
innerhalb der Software.
Der Lizenznehmer erhält nur das individuelle Nutzungsrecht an der Software. Ein
Erwerb von Rechten an der Software selbst ist damit nicht verbunden. Der Lizenzgeber
behält sich alle Veröffentlichungs-, Vervielfältigungs-, Bearbeitungs- und Verwertungsrechte
an der Software vor.
Die folgenden Software-Produkte unterliegen aufgrund von selbständigen Bestandteile oder
entsprechenden Open-Source Lizenzen:
Some icons used in this software are property of Webalys LLC (https://www.webalys.com) and can be used only in the context of this open source project.
phpmailer
GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999
http://phpmailer.codeworxtech.com/
simple_dom_html
Licensed under The MIT License
http://sourceforge.net/projects/simplehtmldom/
full calendar 1.5.3
MIT-LICENSE.txt and GPL-LICENSE.txt respectively
http://arshaw.com/fullcalendar/
jqtouch-1.0-beta-2-r109.zip
http://code.google.com/p/jqtouch/
The MIT License
jQuery UI CSS Framework 1.8.6
Dual licensed under the MIT or GPL Version 2 licenses
http://jquery.org/license
FPDF
FPDF is released under a permissive license: there is no usage restriction. You may embed it freely in your application (commercial or not), with or without modifications.
http://www.fpdf.org/
Math
http://www.gnu.org/licenses/lgpl.txt
http://pear.php.net/package/Math_BigInteger
phpseclib
http://www.gnu.org/licenses/lgpl.txt
http://phpseclib.sourceforge.net
PHP_Compat /
http://www.gnu.org/licenses/lgpl.html
http://php.net/
Spreadsheet_Excel_Reader
http://www.php.net/license/3_0.txt PHP License 3.0
http://www.roxburgh.me.uk
BarcodePHP
http://www.barcodephp.com
On this page, you can download free software for non-commercial use!!!
Crypt
http://www.gnu.org/licenses/lgpl.txt
http://phpseclib.sourceforge.net
pretty autocomplete
COPYING for license information (LGPL)
http://www.horde.org/
jquery.mobile.min.js
The MIT License
http://jquerymobile.com/
colorpicker
COPYING for license information (LGPL).
Original Sphere Plugin v0.1/v0.3, Design/Programming by Ulyses, (c) 2007 ColorJack.com, IE fixes by Hamish.
grider.js
http://www.opensource.org/licenses/mit-license.php
Boris Barroso Camberos
tinymce
GNU LESSER GENERAL PUBLIC LICENSE
moxiecode.com
Flot
http://code.google.com/p/flot/
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
jQuery ZeroClipboard v1.1.1 (am 06.06.2016 aus Version entfernt BS)
http://steamdev.com/zclip
The MIT License
Noty jQuery
https://github.com/needim/noty
The MIT License
HTTPFUL
http://phphttpclient.com
The MIT License
PHPExcel
Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)
http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
Chart.js
MIT license
https://raw.githubusercontent.com/nnnick/Chart.js/v2.0-dev/dist/Chart.js
ckeditor
GNU Lesser General Public License Version 2.1 or later (the "LGPL")
http://www.gnu.org/licenses/lgpl.html
http://ckeditor.com/
Copyright (c) 2003-2018, CKSource Frederico Knabben. All rights reserved.
jQuery.Gantt
Distributed under an MIT license.
http://taitems.github.io/jQuery.Gantt/
jquery-multidownload
MIT License
Copyright (c) 2011 Grzegorz Biesiadecki <gbiesiadecki@gmail.com>
https://github.com/biesiad/jquery-multidownload
barcode
MIT License
Copyright (c) 2016 Kreative Software
https://github.com/kreativekorp/barcode
fints-hbci-php eingebaut am 2.4.2017
MIT License
Copyright (c) 2016 Markus Schindler <mail@markus-schindler.de>
https://github.com/mschindler83/fints-hbci-php
pdfmake
https://github.com/bpampuch/pdfmake
MIT Licence
@bpampuch (founder)
@liborm85
pdfmake is based on a truly amazing library pdfkit (credits to @devongovett).
normalize
http://necolas.github.com/normalize.css/
The MIT License (MIT)
Copyright © Nicolas Gallagher and Jonathan Neal
jquery.timeline
MIT License © Yehia A.Salam
https://github.com/yehiasalam/jquery.timeline
php-fedex-api-wrapper
MIT License © Jeremy Dunn
https://github.com/JeremyDunn/php-fedex-api-wrapper
jQuery tagEditor Plugin
MIT License © Pixabay.com
https://github.com/Pixabay/jQuery-tagEditor
ColorExtractor
MIT License © Mathieu Lechat
https://github.com/thephpleague/color-extractor
HTML Purifier
GNU LESSER GENERAL PUBLIC LICENSE
https://travis-ci.org/ezyang/htmlpurifier
rakit/validation
MIT License
https://github.com/rakit/validation
aura/sqlquery
BSD-2-Clause
https://github.com/auraphp/Aura.SqlQuery
raml2html
MIT License
https://github.com/raml2html/raml2html
raml2html werk theme
MIT License
https://github.com/silicann/raml2html-werk-theme
Nickersoft/push.js
MIT License © Tyler Nickerson
https://github.com/Nickersoft/push.js
sabre/dav
BSD-3-Clause
https://github.com/sabre-io/dav
needim/noty - A jQuery Notification Plugin
MIT License
https://github.com/needim/noty
swiss-payment-slip/swiss-payment-slip-fpdf
MIT License
https://github.com/ravage84/SwissPaymentSlipFpdf
League\Flysystem
MIT License
https://github.com/thephpleague/flysystem
hammerjs/hammer.js
MIT License © Jorik Tangelder (Eight Media)
https://github.com/hammerjs/hammer.js/
haltu/muuri
MIT License © Haltu Oy
https://github.com/haltu/muuri
FitText.js
WTFPL License © Dave Rupert
https://github.com/davatron5000/FitText.js
Smarty template engine
LGPL-3.0 License
https://github.com/smarty-php/smarty
TCPDF
GNU-LGPL v3 © Nicola Asuni - Tecnick.com
https://tcpdf.org
Guzzle, PHP HTTP client
MIT License
https://github.com/guzzle/guzzle
League/OAuth1-Client
MIT License
https://github.com/thephpleague/oauth1-client
Etsy Provider for League/OAuth1-Client
MIT License © Y0lk <gabriel@inkrebit.com>
https://github.com/Y0lk/oauth1-etsy
Lexend Deca Font
SIL Open Font License 1.1 © The Lexend Project Authors
https://github.com/ThomasJockin/lexend
Roboto Font
Apache License, Version 2.0 © Google Inc.
https://github.com/google/roboto
HOTP/TOTP Token Generator
MIT License
https://github.com/lfkeitel/php-totp
Laminas/Laminas-mail
BSD-3-Clause
https://github.com/laminas/laminas-mail
Verwenden Sie die Software nur, wenn Sie mit eine gültige Lizenz erworben haben!
Kontakt: WaWision GmbH
Holzbachstraße 4
D-86152 Augsburg
Telefon 0821 268 41 0 41
Telefax 0821 268 41 0 42
E-Mail kontakt@wawision.de

9
README Normal file
View File

@ -0,0 +1,9 @@
Dies ist die kommerzielle Version von Xentral.
Bitte beachten Sie die Bedingungen aus dem Software-Lizenzvertrag,
welchen Sie beim Kauf unterschrieben haben.
Informationen zur Software finden Sie im Internet:
https://xentral.com/
https://xentral.com/akademie-home

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# Xentral Open-Source Version
Bitte beachten Sie die Bedingungen der [Lizenzen](LICENSE.md).
Informationen zur Software finden Sie hier:
https://xentral.com/
https://xentral.com/akademie-home

View File

@ -0,0 +1,3 @@
#!/bin/sh
mysqldump --extended-insert --no-create-db dbname -hlocalhost -uwawision -p | gzip > /var/www/backup/mysql/mysql_complete_`date +%d`.gz

View File

@ -0,0 +1,10 @@
#!/bin/sh
mysqldump dbname -h127.0.0.1 -u<username> -p<password> | gzip > /tmp/mysql_complete_daily.gz
ftp -inv <ipftpserver> << EOF
user <userftp> <passwordftp>
put /tmp/mysql_complete_daily.gz mysql_complete_`date +%y%m%d_0000`.gz
bye
EOF
rm -f /tmp/mysql_complete_daily.gz

View File

@ -0,0 +1,10 @@
#!/bin/sh
tar cfz /tmp/userdata.tar.gz ../../userdata
ftp -inv <ipftpserver> << EOF
user <userftp> <passwordftp>
put /tmp/userdata.tar.gz userdata_complete_`date +%d`.tar.gz
bye
EOF
rm -f /tmp/userdata.tar.gz

34
backup/mysql_ftp.php.tpl Normal file
View File

@ -0,0 +1,34 @@
<?php
$WFdbname='';
$WFdbuser='';
$WFdbpass='';
$ftp_server="";
$ftp_user_name = "";
$ftp_user_pass= "";
$pfad = "/tmp";
exec("mysqldump $WFdbname -hlocalhost -u".$WFdbuser." -p".$WFdbpass." | gzip > $pfad/mysql_complete.gz");
$file = $pfad.'/mysql_complete.gz';
$remote_file = "mysql_complete_".date('d').".gz";
// Verbindung aufbauen
$conn_id = ftp_connect($ftp_server);
// Login mit Benutzername und Passwort
$login_result = ftp_login($conn_id, $ftp_user_name, $ftp_user_pass);
//ftp_pasv($conn_id, true);
// Datei hochladen
if (ftp_put($conn_id, $remote_file, $file, FTP_BINARY)) {
} else {
}
// Verbindung schlie?~_en
ftp_close($conn_id);
unlink($file);
?>

View File

@ -0,0 +1,35 @@
<?php
$ftp_server="";
$ftp_user_name = "";
$ftp_user_pass= "";
$pfad = "/tmp";
$userdata = "/var/www/wawision/userdata";
exec("tar cfz $pfad/userdata.tar.gz $userdata");
$file = $pfad.'/userdata.tar.gz';
$remote_file = "userdata_".date('d').".tar.gz";
// Verbindung aufbauen
$conn_id = ftp_connect($ftp_server);
// Login mit Benutzername und Passwort
$login_result = ftp_login($conn_id, $ftp_user_name, $ftp_user_pass);
ftp_pasv($conn_id, true);
// Datei hochladen
if (ftp_put($conn_id, $remote_file, $file, FTP_BINARY)) {
} else {
}
// Verbindung schlie?~_en
ftp_close($conn_id);
unlink($file);
?>

View File

@ -0,0 +1,56 @@
<?php
namespace Xentral\Components\Backup\Adapter;
use Xentral\Components\Database\DatabaseConfig;
/**
* Interface AdapterInterface
*
* @package Xentral\Components\Backup\Adapter
*/
interface AdapterInterface
{
/** @var string STATUS_WORKING */
const STATUS_WORKING = 'working';
/** @var string STATUS_WAIT */
const STATUS_WAITING = 'waiting';
/**
* Makes MySQL DUMP
*
* @param DatabaseConfig $config
*
* @param string $file
*
* @param null|string|array $sTable
*
* @param null|string $where
*
* @param bool $quickMode Without SET INNODB_STRICT_MODE=0; Advantage quick and space-saving
*
* @return int PidFile
*/
public function createDump(DatabaseConfig $config, $file, $sTable = null, $where = null, $quickMode=true);
/**
* Makes Backup or System template recovery
*
* @param DatabaseConfig $config
* @param string $file
*
* @return int pidFile
*/
public function restoreDump(DatabaseConfig $config, $file);
/**
* returns the current status
*
* @param string $pidFile
*
* @return string|self::STATUS_WORKING|self::STATUS_WAITING
*/
public function getStatus($pidFile);
}

View File

@ -0,0 +1,83 @@
<?php
namespace Xentral\Components\Backup\Adapter;
use Xentral\Components\Database\DatabaseConfig;
final class ExecAdapter implements AdapterInterface
{
/** @var DatabaseConfig $config */
private $config;
/** @var int timeout */
const TIME_OUT = 3600;
/**
* @param DatabaseConfig $config
* @param string $file
*
* @param null|string|array $tables
*
* @param null|string $where
*
* @param bool $quickMode Without SET INNODB_STRICT_MODE=0; Advantage quick and space-saving
*
* @return void
*/
public function createDump(DatabaseConfig $config, $file, $tables = null, $where = null, $quickMode = true)
{
$this->config = $config;
$sAsBackup = $this->config->getDatabase();
if ($tables !== null) {
if (is_array($tables)) {
$tables = implode(' ', $tables);
}
$sAsBackup .= ' --tables ' . $tables;
}
if ($where !== null) {
$sAsBackup .= " --where=\"$where\"";
}
if ($quickMode !== true) {
$cmd = "echo 'SET INNODB_STRICT_MODE=0;' > {$file} && mysqldump --no-tablespaces --extended-insert {$sAsBackup} --no-create-db -h{$this->config->getHostname()} -u{$this->config->getUsername()} -p'{$this->config->getPassword()}' >> {$file} && gzip -c {$file} > " . $file . '.gz && rm -f' . $file;
} else {
$cmd = "mysqldump --no-tablespaces --extended-insert {$sAsBackup} --no-create-db -h{$this->config->getHostname()} -u{$this->config->getUsername()} -p'{$this->config->getPassword()}' | gzip > " . $file . '.gz';
}
$this->execute($cmd);
}
/**
* @param DatabaseConfig $config
* @param string $file
*
* @return void
*/
public function restoreDump(DatabaseConfig $config, $file)
{
$this->config = $config;
$cmd = "gunzip < {$file} | mysql -D{$this->config->getDatabase()} -h{$this->config->getHostname()} -u{$this->config->getUsername()} -p'{$this->config->getPassword()}'";
$this->execute($cmd);
}
/**
* @param string $pidFile
*
* @return string
*/
public function getStatus($pidFile)
{
if (file_exists($pidFile) && ($time = file_get_contents($pidFile)) && (time() - (int)$time) < static::TIME_OUT) {
return AdapterInterface::STATUS_WORKING;
}
return AdapterInterface::STATUS_WAITING;
}
/**
* @param string $cmd
*/
protected function execute($cmd)
{
@exec($cmd);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Xentral\Components\Backup;
use ApplicationCore;
use Xentral\Components\Backup\Exception\BackupException;
use Xentral\Components\Backup\Adapter\ExecAdapter;
use Xentral\Components\Backup\Logger\BackupLog;
use Xentral\Core\DependencyInjection\ContainerInterface;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'DatabaseBackup' => 'onInitDatabaseBackup',
'FileBackup' => 'onInitFileBackup',
'BackupLog' => 'onInitBackupLogger',
];
}
/**
*
* @param ContainerInterface $container
*
* @return DatabaseBackup
*/
public static function onInitDatabaseBackup(ContainerInterface $container)
{
//@codeCoverageIgnoreStart
if (!function_exists('exec')) {
throw new BackupException(sprintf('function "%s" is missing!', 'exec'));
}
//@codeCoverageIgnoreEnd
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new DatabaseBackup(new ExecAdapter(), $app->erp->getTMP());
}
/**
* @param ContainerInterface $container
*
* @return FileBackup
*/
public static function onInitFileBackup(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new FileBackup($container->get('BackupLog'), $app->erp->getTMP());
}
public static function onInitBackupLogger(ContainerInterface $container)
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
/** @var string $path */
$path = $app->erp->GetRootPath() . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR;
return new BackupLog($path, 'status.txt');
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace Xentral\Components\Backup;
use Xentral\Components\Backup\Adapter\AdapterInterface;
use Xentral\Components\Database\DatabaseConfig;
use Xentral\Components\Backup\Exception\BackupException;
final class DatabaseBackup
{
/**
* @var AdapterInterface
*/
private $adapter;
/** @var string $tmpPath */
private $tmpPath;
/** @var string lock */
const PID_NAME = 'backup.lock';
/**
* DatabaseBackup constructor.
*
* @param AdapterInterface $adapter
*
* @param string $tmpPath
*/
public function __construct(AdapterInterface $adapter, $tmpPath)
{
$this->adapter = $adapter;
$this->tmpPath = $tmpPath;
}
/**
* Creates MySQL Dump
*
* @param DatabaseConfig $config
* @param string $file
* @param null|string|array $sTable
*
* @param null|string $where
*
* @return void
*/
public function createDump(DatabaseConfig $config, $file, $sTable = null, $where = null)
{
$sPidFile = $this->getLockFile();
file_put_contents($sPidFile, time());
$this->adapter->createDump($config, $file, $sTable, $where);
@unlink($sPidFile);
}
/**
* Restores Database DUMP
*
* @param DatabaseConfig $config
* @param string $file
*
* @return void
*/
public function restoreDump(DatabaseConfig $config, $file)
{
if (!file_exists($file)) {
throw new BackupException(sprintf('Database Dump %s not found!', $file));
}
$sPidFile = $this->getLockFile();
file_put_contents($sPidFile, time());
$this->adapter->restoreDump($config, $file);
@unlink($sPidFile);
}
/**
* @param string $metaFile
*
* @return string|null
*/
public function getMetaInfo($metaFile)
{
if (!empty($metaFile) && file_exists($metaFile) && ($sMetaEnc = file_get_contents($metaFile))) {
return $this->decodeJson(base64_decode($sMetaEnc), true);
}
return null;
}
/**
* @param string $sJSON
* @param bool $bAsHash
*
* @return mixed|null
*/
protected function decodeJson($sJSON, $bAsHash = false)
{
if (($xData = json_decode($sJSON, $bAsHash)) !== null
&& (json_last_error() === JSON_ERROR_NONE)) {
return $xData;
}
return null;
}
/**
*
* @return string|AdapterInterface
*/
public function getLockStatus()
{
return $this->adapter->getStatus($this->getLockFile());
}
/**
* @return string
*/
protected function getLockFile()
{
return rtrim($this->tmpPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . static::PID_NAME;
}
/**
* @param array $tables
* @param array $excludeKeys
*
* @return mixed
*/
public function excludeCheckSumTables($tables, $excludeKeys = [])
{
$default = [
'backup',
'useronline',
'logfile',
'cronjob_starter_running',
'wiki',
'protokoll',
'cronjob_log',
'module_stat',
'checkaltertable',
'konfiguration',
'permissionhistory',
'adapterbox_request_log',
'hook',
'module_action',
'prozessstarter',
'sqlcache',
'systemhealth',
'userkonfiguration',
'artikel',
'shopimport_amazon_throttling',
'lieferschein',
'report_column',
'report_parameter',
'notification_message',
'module_stat_detail',
];
$excludeKeys = array_merge($default, $excludeKeys);
foreach ($excludeKeys as $key) {
if (!array_key_exists($key, $tables)) {
continue;
}
unset($tables[$key]);
}
return $tables;
}
/**
* @param string $backupFile
*
* @return string
*/
public function getMetaFileName($backupFile)
{
$asFile = explode('.', $backupFile);
array_pop($asFile);
$filename = implode('.', $asFile) . '.meta';
return str_replace('.backup', 'backup/snapshots', $filename);
}
/**
* @param string $filePath
*
* @return string|null
*/
public function getDumpMetaData($filePath = null)
{
return $this->getMetaInfo($this->getMetaFileName($filePath));
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Backup\Exception;
use RuntimeException;
class BackupException extends RuntimeException implements BackupExceptionInterface
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace Xentral\Components\Backup\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
/**
* Interface BackupExceptionInterface
*
* @package Xentral\Components\Backup\Exception
*/
interface BackupExceptionInterface extends ComponentExceptionInterface
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace Xentral\Components\Backup\Exception;
use RuntimeException;
class LogException extends RuntimeException implements BackupExceptionInterface
{
}

View File

@ -0,0 +1,451 @@
<?php
namespace Xentral\Components\Backup;
use PHPUnit\Runner\Exception;
use Xentral\Components\Backup\Logger\BackupLog;
use Xentral\Components\Backup\Exception\BackupException;
use ZipArchive;
final class FileBackup implements FileBackupInterface
{
/** @var string backup path */
private $sUserPath;
/** @var BackupLog $logger */
private $logger;
/** @var string $cacheTmp */
private $cacheTmp;
/**
* @param BackupLog $logger
* @param string $cacheTmp
*/
public function __construct(BackupLog $logger, $cacheTmp)
{
$this->logger = $logger;
$this->cacheTmp = $cacheTmp;
}
/**
* @return string
*/
protected function getMainPath()
{
$asPath = explode(DIRECTORY_SEPARATOR, $this->sUserPath);
array_pop($asPath);
return implode(DIRECTORY_SEPARATOR, $asPath) . DIRECTORY_SEPARATOR;
}
/**
* returns the full file name path
*
* @param string $filename
* @param bool $bIsSnapshots
* @param null $userPath
*
* @throws BackupException
* @return string
*/
public function getLocalPath($filename, $userPath = null, $bIsSnapshots = true)
{
if (null !== $userPath) {
$this->sUserPath = $userPath;
}
$path = $this->getMainPath();
if ($bIsSnapshots === true) {
$path .= FileBackupInterface::SNAPSHOTS_FOLDER . DIRECTORY_SEPARATOR;
}
if (!file_exists($path) && !@mkdir($path) && !is_dir($path)) {
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $path));
throw new BackupException(sprintf('Directory "%s" was not created', $path));
}
return $path . $filename;
}
/**
* @return string
*/
private function tmpDir()
{
return $this->getMainPath() . 'backup/.backup' . DIRECTORY_SEPARATOR;
}
/**
* @return string
*/
public function getSnapshotsDir()
{
return $this->getMainPath() . FileBackupInterface::SNAPSHOTS_FOLDER . DIRECTORY_SEPARATOR;
}
/**
* @param $path
*
* @return false|int
*/
protected function addLock($path)
{
return $this->logger->write(time(), $path, FileBackupInterface::PID_FILE, false, false);
}
/**
* @return bool
*/
private function tryPurgePidFile()
{
$pidFile = $this->tmpDir() . FileBackupInterface::PID_FILE;
$time = file_get_contents($pidFile);
if ((time() - (int)$time > FileBackupInterface::TIME_OUT)) {
return unlink($pidFile);
}
return false;
}
/**
* @param string|null $userPath
*
* @throws BackupException
* @return string|null
*/
public function begin($userPath = null)
{
if (null !== $userPath) {
$this->sUserPath = $userPath;
}
$path = $this->tmpDir();
if (is_dir($path)) {
@exec('rm -rf ' . $path);
}
$backupDir = $this->getMainPath() . 'backup';
if (file_exists($backupDir . DIRECTORY_SEPARATOR . 'status.txt')) {
@unlink($backupDir . DIRECTORY_SEPARATOR . 'status.txt');
}
if (file_exists($backupDir . DIRECTORY_SEPARATOR . 'session.txt')) {
@unlink($backupDir . DIRECTORY_SEPARATOR . 'session.txt');
}
if (!file_exists($path) && !@mkdir($path, 0777, true) && !is_dir($path)) {
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $path));
throw new BackupException(sprintf('Directory "%s" was not created', $path));
}
if ($this->getLockStatus() === FileBackupInterface::STATUS_WORKING && $this->tryPurgePidFile() === false) {
return null;
//throw new BackupException(sprintf('Backup is Running'));
}
if ($this->addLock($path) === false) {
$this->logger->writePersistent('Failed start backup');
throw new BackupException('Failed start backup');
}
return $path;
}
/**
* @param string $file
*
* @return bool
*/
protected function cleanUp($file)
{
$path = $this->tmpDir();
if ($this->moveDir($path . $file, $this->getLocalPath($file)) === true) {
return $this->deleteDir($path);
}
$this->logger->writePersistent(sprintf('Clean Up of %s failed', $path));
throw new BackupException(sprintf('Clean Up of %s failed', $path));
}
/**
* @param string $class_name
*
* @return bool
*/
protected function classExists($class_name)
{
return class_exists($class_name);
}
/**
* @param ZipArchive $oZip
* @param string $fileName
* @param int $flags
*
* @return mixed
*/
protected function openZipObject($oZip, $fileName, $flags = 0)
{
return $oZip->open($fileName, $flags);
}
/**
* @param string $oldDir
* @param string $newDir
*
* @return bool
*/
protected function moveDir($oldDir, $newDir)
{
return @rename($oldDir, $newDir);
}
/**
* @param string $dir
*
* @return bool
*/
protected function isDir($dir)
{
return @mkdir($dir) || is_dir($dir);
}
/**
* @return string
*/
public function getBackupExtension()
{
return FileBackupInterface::COMPRESS_EXTENSION;
}
/**
* @param string $filename Zipped file name
* @param string $userPath local directory to backup
* @param string|null $sMySQLFile MySQL Backup file
*
* @return bool
*/
public function createBackup($filename, $userPath, $sMySQLFile = null)
{
$this->sUserPath = $userPath;
$rootPath = realpath($userPath);
if (!file_exists($userPath)) {
$this->logger->writePersistent(sprintf('Directory "%s" was not found', $userPath));
throw new BackupException(sprintf('Directory "%s" was not found', $userPath));
}
$tmpFilename = $this->tmpDir() . $filename;
$sMySQLFullPath = $this->tmpDir() . $sMySQLFile;
if (null !== $sMySQLFile && is_file($sMySQLFullPath) && filesize($sMySQLFullPath) > 1024) {
$this->logger->write('Add MySQL file to Zip');
exec('cd ' . $rootPath . ' && mv ' . $sMySQLFullPath . ' ' . $sMySQLFile);
}
exec('cd ' . $rootPath . ' && zip -r -9 ' . $tmpFilename . ' * .[^.]* -x "wiki/*"');
if (null !== $sMySQLFile) {
exec('cd ' . $rootPath . ' && rm -f ' . $sMySQLFile);
}
return $this->cleanUp($filename);
}
/**
* @param string $dirPath
*
* @return bool
*/
private function deleteDir($dirPath)
{
if (is_dir($dirPath)) {
if (substr($dirPath, strlen($dirPath) - 1, 1) !== '/') {
$dirPath .= '/';
}
$files = glob($dirPath . '*', GLOB_MARK);
foreach ($files as $file) {
if (is_dir($file)) {
$this->deleteDir($file);
} else {
unlink($file);
}
}
return rmdir($dirPath);
}
$this->logger->writePersistent(sprintf('Deleted DIR %s failed', $dirPath));
throw new BackupException(sprintf('Deleted DIR %s failed', $dirPath));
}
/**
* @param string $backupFile
* @param string $userPath
* @param array $options
*
* @return bool
*/
public function restoreFileSystem($backupFile, $userPath, $options = [])
{
$default = ['template_file_dir' => null, 'exclude_dir' => ['wiki']];
$options = array_merge($default, $options);
$templateFileDir = $options['template_file_dir'];
$this->sUserPath = $userPath;
$bIsSnapshots = null === $templateFileDir;
$this->sUserPath = null === $templateFileDir ? $userPath : $templateFileDir;
if (file_exists($file = $this->getLocalPath($backupFile, null, $bIsSnapshots))) {
$userDataPath = realpath($userPath);
$tmpExtract = $this->tmpDir() . FileBackupInterface::LOCAL_FILES_DIR_NAME . 'tmp';
if (!file_exists($tmpExtract) && !@mkdir($tmpExtract) && !is_dir($tmpExtract)) {
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $tmpExtract));
throw new BackupException(sprintf('Directory "%s" was not created', $tmpExtract));
}
if (!$this->classExists('ZipArchive')) {
$this->logger->writePersistent('Class ZipArchive is missing!');
throw new BackupException('Class ZipArchive is missing!');
}
$oZip = new ZipArchive();
if ($this->openZipObject($oZip, $file, ZipArchive::CHECKCONS) !== true) {
$this->logger->writePersistent(sprintf('Failure to open file in "%s"', $file));
throw new BackupException(sprintf('Failure to open file in "%s"', $file));
}
$oZip->extractTo($tmpExtract);
$oZip->close();
// move user data
$shortTmp = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '.rmTmp';
$sBeforeTmp = $shortTmp . uniqid('', true) . 'before';
if (!$this->isDir($sBeforeTmp)) {
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $sBeforeTmp));
throw new BackupException(sprintf('Directory "%s" was not created', $sBeforeTmp));
}
$this->logger->write('Moving userdata away');
if (!$this->moveDir($userDataPath, $sBeforeTmp)) {
$this->logger->writePersistent(sprintf('Moving %s into %s failed! ', $userDataPath, $sBeforeTmp));
throw new BackupException(sprintf('Moving %s into %s failed! ', $userDataPath, $sBeforeTmp));
}
if (array_key_exists('exclude_dir', $options) && is_array($options['exclude_dir'])) {
$this->excludeDirectory($options['exclude_dir'], $tmpExtract, $sBeforeTmp);
}
$this->logger->write('Recovering userData');
if (!$this->moveDir($tmpExtract, $userDataPath)) {
$this->logger->writePersistent(sprintf('Moving %s into %s failed!', $tmpExtract, $userDataPath));
throw new BackupException(sprintf('Moving %s into %s failed!', $tmpExtract, $userDataPath));
}
// FIX TMP ISSUE
if (!empty($this->cacheTmp) && is_dir($this->cacheTmp)) {
$this->logger->write('Delete DB tmp');
$this->deleteDir($this->cacheTmp);
}
// remove DB if exists
$backupFileExploded = explode('.', $backupFile);
array_pop($backupFileExploded);
$tmpSql = implode('.', $backupFileExploded) . '.sql.gz';
if (is_file($userDataPath . DIRECTORY_SEPARATOR . $tmpSql)) {
exec('cd ' . $userDataPath . ' && rm -f ' . $tmpSql);
}
return $this->deleteDir($this->tmpDir()) && $this->deleteDir($sBeforeTmp);
}
return false;
}
/**
* @param array $excludeDir
* @param string $tmpDir extracted temporally directory
* @param string $oldUserDataDir
*/
protected function excludeDirectory($excludeDir = [], $tmpDir, $oldUserDataDir)
{
foreach ($excludeDir as $directory) {
// EXCLUDE WIKI DIRECTORY
$keepPath = $tmpDir . DIRECTORY_SEPARATOR . $directory;
$keepDirTmp = $oldUserDataDir . DIRECTORY_SEPARATOR . $directory;
if (!is_dir($keepPath) && $directory !== 'wiki') {
$this->logger->writePersistent(sprintf('Directory "%s" cannot be skipped', $keepPath));
throw new BackupException(sprintf('Directory "%s" cannot be skipped', $keepPath));
}
if ($directory !== 'wiki') {
$this->deleteDir($keepPath);
}
if (file_exists($keepDirTmp)) {
$oldDirTmp = rtrim(
sys_get_temp_dir(),
DIRECTORY_SEPARATOR
) . DIRECTORY_SEPARATOR . '.' . $directory . 'Tmp' . uniqid('', true);
if (!$this->isDir($oldDirTmp)) {
$this->logger->writePersistent(sprintf('Directory "%s" was not created', $oldDirTmp));
throw new BackupException(sprintf('Directory "%s" was not created', $oldDirTmp));
}
$oldDir = $oldDirTmp . DIRECTORY_SEPARATOR . $directory;
if (!$this->moveDir($keepDirTmp, $oldDir)) {
$this->logger->writePersistent(sprintf('Could not move %s directory into "%s"', $directory,
$oldDir));
throw new BackupException(sprintf('Could not move %s directory into "%s"', $directory,
$oldDir));
}
}
// Reset Latest WIKI Directory
if (isset($oldDir) && is_dir($oldDir)) {
$this->logger->write('Reset Wiki Directory');
if (!$this->moveDir($oldDir, $keepPath)) {
$this->logger->writePersistent(sprintf('Could not move %s directory into "%s"', $oldDir,
$keepPath));
throw new BackupException(sprintf('Could not move %s directory into "%s"', $oldDir, $keepPath));
}
}
}
}
/**
* @param string|null $userDataDir
*
* @return string
*/
public function getLockStatus($userDataDir = null)
{
if (null !== $userDataDir) {
$this->sUserPath = $userDataDir;
}
$path = $this->tmpDir();
if (file_exists($path . FileBackupInterface::PID_FILE) &&
($time = file_get_contents($path . FileBackupInterface::PID_FILE)) &&
(time() - (int)$time < FileBackupInterface::TIME_OUT)
) {
return FileBackupInterface::STATUS_WORKING;
}
return FileBackupInterface::STATUS_WAITING;
}
/**
* Clean everything without files move. This might be used, when breaking started backup job
*
* @return bool
*/
public function breakCleanUp()
{
return $this->deleteDir($this->tmpDir());
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Xentral\Components\Backup;
use Xentral\Components\Backup\Exception\BackupException;
interface FileBackupInterface
{
/** @var string STATUS_WAIT */
const STATUS_WAITING = 'waiting';
/** @var string STATUS_WORKING */
const STATUS_WORKING = 'working';
/** @var string Extension for the whole backup */
const COMPRESS_EXTENSION = 'zip';
/** @var string pid file */
const PID_FILE = 'backup.lock';
/** @var int Timeout */
const TIME_OUT = 3600;
/** @var string snapshots folder */
const SNAPSHOTS_FOLDER = 'backup/snapshots';
/** @var string user data directory */
const LOCAL_FILES_DIR_NAME = 'userdata';
/**
* @param string|null $userPath
*
* @throws BackupException
* @return string|null
*/
public function begin($userPath = null);
/**
* @param string $filename
* @param string $userPath
* @param string|null $sMySQLFile
*
* @return bool
*/
public function createBackup($filename, $userPath, $sMySQLFile = null);
/**
* @param string $backupFile
* @param string $userPath
* @param array $options
*
* @return bool
*/
public function restoreFileSystem($backupFile, $userPath, $options = []);
/**
* @return string
*/
public function getLockStatus();
}

View File

@ -0,0 +1,172 @@
<?php
namespace Xentral\Components\Backup\Logger;
use Xentral\Components\Backup\Exception\LogException;
final class BackupLog
{
/** @var string|null $path */
private $fullPath;
/** @var string|null $storagePath */
private $storagePath;
/** @var string|null $fileName */
private $fileName;
/** @var string $persistentFile */
private static $persistentFile = 'backup_logger.txt';
public function __construct($path = null, $fileName = null)
{
if (null !== $path && null !== $fileName) {
$this->fullPath = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
}
$this->storagePath = $path;
$this->fileName = $fileName;
}
/**
* @param string $message
*
* @param string|null $path
* @param string|null $fileName
*
* @param bool $withDate
*
* @param bool $append
*
* @return false|int
*/
public function write($message, $path = null, $fileName = null, $withDate = true, $append = true)
{
$flag = FILE_APPEND | LOCK_EX;
$path = $this->getFullPath($path, $fileName);
if (null === $path || (!file_exists($path) && !@touch($path))) {
throw new LogException(sprintf('cannot access or create file %s', $path));
}
$message = $withDate === true ? time() . ': ' . $message : $message;
if ($append === false) {
$flag &= ~FILE_APPEND;
}
return file_put_contents($path, $message . "\n", $flag);
}
/**
* @param int $linePosition
*
* @param string|null $path
*
* @param string|null $fileName
*
* @return mixed|string
*/
public function tail($linePosition = 0, $path = null, $fileName = null)
{
$path = $this->getFullPath($path, $fileName);
if (null === $path || !file_exists($path)) {
throw new LogException(sprintf('File %s cannot be found!', $path));
}
$output = '';
if (($xData = file($path, FILE_SKIP_EMPTY_LINES)) && count($xData) > 0) {
$key = (int)$linePosition === 0 ? count($xData) - 1 : $linePosition;
if (!array_key_exists($key, $xData)) {
throw new LogException(sprintf('Offset %d is missing', (int)$linePosition));
}
$output = $xData[$key];
}
return $output;
}
/**
* @param string|null $path
*
* @param string|null $fileName
*
* @return bool|false|string
*/
public function getContent($path = null, $fileName = null)
{
$path = $this->getFullPath($path, $fileName);
if (null === $path || !file_exists($path)) {
throw new LogException(sprintf('File %s cannot be found!', $path));
}
return file_get_contents($path);
}
/**
* @param null $path
*
* @param string|null $fileName
*
* @throws LogException
* @return bool
*/
public function delete($path = null, $fileName = null)
{
$path = $this->getFullPath($path, $fileName);
if (null !== $path && !file_exists($path)) {
return false;
//throw new LogException(sprintf('File %s cannot be deleted!', $path));
}
return unlink($path);
}
/**
* @param null $path
* @param null $fileName
*
* @return string|null
*/
private function getFullPath($path = null, $fileName = null)
{
if ($path === null && $fileName === null) {
return $this->fullPath;
}
if ($path !== null && $fileName !== null) {
return rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
}
if ($fileName !== null && $path === null && $this->storagePath !== null) {
return rtrim($this->storagePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $fileName;
}
if ($path !== null && $fileName === null && $this->fileName !== null) {
return rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->fileName;
}
return null;
}
/**
* @param string $message
*
* @return void
*/
public function writePersistent($message)
{
$this->write($message, null, self::$persistentFile, true, false);
}
/**
* @return string
*/
public function getPersistentFileName()
{
return self::$persistentFile;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Xentral\Components\Barcode;
use Xentral\Components\Barcode\Exception\InvalidArgumentException;
final class BarcodeFactory
{
/**
* @param string $codeText
* @param string $ecLevel Error correction level [L|M|Q|H]
*
* @return Qrcode
*/
public function createQrCode($codeText, $ecLevel = 'L')
{
$codeType = 'QRCODE,' . $ecLevel;
if (!in_array($codeType, Qrcode::$validTypes, true)) {
throw new InvalidArgumentException('Invalid error correction level: ' . $ecLevel);
}
$barcode2d = new TcpdfBarcode2d($codeType, $codeText);
return new Qrcode($barcode2d);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Xentral\Components\Barcode;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'BarcodeFactory' => 'onInitBarcodeFactory',
];
}
/**
* @return BarcodeFactory
*/
public static function onInitBarcodeFactory()
{
return new BarcodeFactory();
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Barcode\Exception;
use RuntimeException;
class BarcodeCreationFailedException extends RuntimeException implements BarcodeExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Barcode\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface BarcodeExceptionInterface extends ComponentExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Barcode\Exception;
use InvalidArgumentException as SplInvalidArgumentException;
class InvalidArgumentException extends SplInvalidArgumentException implements BarcodeExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Barcode\Exception;
use RuntimeException;
class MissingPhpExtensionException extends RuntimeException implements BarcodeExceptionInterface
{
}

View File

@ -0,0 +1,102 @@
<?php
namespace Xentral\Components\Barcode;
final class Qrcode
{
/** @var string TYPE_QRCODE */
const TYPE_DEFAULT = 'QRCODE';
/** @var string TYPE_EC_LOW Low error correction */
const TYPE_EC_LOW = 'QRCODE,L';
/** @var string TYPE_EC_MEDIUM Medium error correction */
const TYPE_EC_MEDIUM = 'QRCODE,M';
/** @var string TYPE_EC_QUARTILE Better error correction */
const TYPE_EC_QUARTILE = 'QRCODE,Q';
/** @var string TYPE_EC_HIGH Best error correction */
const TYPE_EC_HIGH = 'QRCODE,H';
/** @var array $validTypes */
public static $validTypes = [
self::TYPE_DEFAULT,
self::TYPE_EC_LOW,
self::TYPE_EC_MEDIUM,
self::TYPE_EC_QUARTILE,
self::TYPE_EC_HIGH,
];
/** @var TcpdfBarcode2d $barcode */
private $barcode;
/**
* @param TcpdfBarcode2d $barcode
*/
public function __construct(TcpdfBarcode2d $barcode)
{
$this->barcode = $barcode;
}
/**
* @return string
*/
public function getText()
{
return $this->barcode->getText();
}
/**
* Returns the QR code as array representation
*
* @return array
*/
public function toArray()
{
return $this->barcode->getBarcodeArray();
}
/**
* Returns the QR code as HTML representation
*
* @param int $width Width of a single rectangle element in pixels.
* @param int $height Height of a single rectangle element in pixels.
* @param string $color Foreground color for bar elements (background is transparent).
*
* @return string HTML code
*/
public function toHtml($width = 10, $height = 10, $color = 'black')
{
return $this->barcode->getBarcodeHtml($width, $height, $color);
}
/**
* Returns the QR code as SVG document
*
* @param int $width Width of a single rectangle element in user units
* @param int $height Height of a single rectangle element in user units
* @param string $color Foreground color (in SVG format) for bar elements (background is transparent)
*
* @return string SVG document
*/
public function toSvg($width = 10, $height = 10, $color = 'black')
{
return $this->barcode->getBarcodeSvg($width, $height, $color);
}
/**
* Returns the QR code as PNG image (requires GD or Imagick library)
*
* @param int $width Width of a single rectangle element in pixels
* @param int $height Height of a single rectangle element in pixels
* @param array $color RGB-Array (0-255) foreground color for bar elements (background is transparent)
*
* @return string Image as string
*/
public function toPng($width = 10, $height = 10, $color = [0, 0, 0])
{
return $this->barcode->getBarcodePng($width, $height, $color);
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace Xentral\Components\Barcode;
use TCPDF2DBarcode;
use Xentral\Components\Barcode\Exception\BarcodeCreationFailedException;
use Xentral\Components\Barcode\Exception\InvalidArgumentException;
use Xentral\Components\Barcode\Exception\MissingPhpExtensionException;
/**
* Anti-Corruption-Layer for TCPDF2DBarcode class
*/
final class TcpdfBarcode2d
{
/** @var array $validTypes */
public static $validTypes = [
Qrcode::TYPE_DEFAULT,
Qrcode::TYPE_EC_LOW,
Qrcode::TYPE_EC_MEDIUM,
Qrcode::TYPE_EC_QUARTILE,
Qrcode::TYPE_EC_HIGH,
];
/** @var TCPDF2DBarcode $barcode */
private $barcode;
/** @var string $codeType */
private $type;
/** @var string $codeText */
private $text;
/**
* @param string $type
* @param string $text
*
* @throws InvalidArgumentException
* @throws BarcodeCreationFailedException
*/
public function __construct($type, $text)
{
if (empty($type)) {
throw new InvalidArgumentException('Could not create barcode. Required parameter "type" is empty.');
}
if (empty($text)) {
throw new InvalidArgumentException('Could not create barcode. Required parameter "text" is empty.');
}
if (!in_array($type, self::$validTypes, true)) {
throw new InvalidArgumentException(sprintf(
'Could not create barcode. Invalid Type: "%s". Valid types: [%s]',
$type,
implode('|', self::$validTypes)
));
}
$barcode = new TCPDF2DBarcode($text, $type);
if ($barcode->getBarcodeArray() === false) {
throw new BarcodeCreationFailedException(sprintf(
'Could not create barcode. Type "%s" - Text "%s"', $type, $text
));
}
$this->barcode = $barcode;
$this->text = $text;
$this->type = $type;
}
/**
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return array
*/
public function getBarcodeArray()
{
return $this->barcode->getBarcodeArray();
}
/**
* Returns the QR code as HTML representation
*
* @param int $width Width of a single rectangle element in pixels.
* @param int $height Height of a single rectangle element in pixels.
* @param string $color Foreground color for bar elements (background is transparent).
*
* @return string HTML code
*/
public function getBarcodeHtml($width = 10, $height = 10, $color = 'black')
{
return $this->barcode->getBarcodeHTML($width, $height, $color);
}
/**
* Returns the QR code as SVG document
*
* @param int $width Width of a single rectangle element in user units
* @param int $height Height of a single rectangle element in user units
* @param string $color Foreground color (in SVG format) for bar elements (background is transparent)
*
* @return string SVG document
*/
public function getBarcodeSvg($width = 10, $height = 10, $color = 'black')
{
return $this->barcode->getBarcodeSVGcode($width, $height, $color);
}
/**
* Returns the QR code as PNG image (requires GD or Imagick library)
*
* @param int $width Width of a single rectangle element in pixels
* @param int $height Height of a single rectangle element in pixels
* @param array $color RGB-Array (0-255) foreground color for bar elements (background is transparent)
*
* @throws MissingPhpExtensionException If gd and imagick extension missing
* @throws BarcodeCreationFailedException
*
* @return string Image as string
*/
public function getBarcodePng($width, $height, $color = [0, 0, 0])
{
$imageData = $this->barcode->getBarcodePngData($width, $height, $color);
if (!function_exists('imagecreate') && !extension_loaded('imagick')) {
throw new MissingPhpExtensionException(
'Barcode image creation failed. PHP extension "gd" or "imagick" is required; both missing.'
);
}
if ($imageData === false) {
throw new BarcodeCreationFailedException(
'Barcode image creation failed. Unknown error.'
);
}
return $imageData;
}
}

View File

@ -0,0 +1,25 @@
# Barcodes
## QR-Codes
### Beispiel
```php
$factory = $this->app->Container->get('BarcodeFactory');
// Qrcode-Objekt erzeugen
$ecLevel = 'M'; // M = Medium error correction
$qrcode = $factory->createQrCode($qrtext, $ecLevel);
// Varianten für die Ausgabe
$html = $qrcode->toHtml($width, $height, $color);
$svg = $qrcode->toSvg($width, $height, $color);
$png = $qrcode->toPng($width, $height, $color);
```
##### Fehlerkorrektur-Levels
* `L` = Low / Niedrige Fehlerkorrektur (Default)
* `M` = Medium / Mittlere Fehlerkorrektur
* `Q` = Quartile / Bessere Fehlerkorrektur
* `H` = High / Höchste Fehlerkorrektur

View File

@ -0,0 +1,197 @@
<?php
namespace Xentral\Components\Database\Adapter;
use Generator;
interface AdapterInterface
{
/**
* @return void
*/
public function connect();
/**
* @return void
*/
public function disconnect();
/**
* @return bool
*/
public function inTransaction();
/**
* @return void
*/
public function beginTransaction();
/**
* @return void
*/
public function rollback();
/**
* @return void
*/
public function commit();
/**
* @return int
*/
public function lastInsertId();
/**
* @param array $values
* @param string $statement
*
* @return void
*/
public function perform($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return int
*/
public function fetchAffected($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAll($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAssoc($statement, array $values = []);
/**
* @param string $statement
* @param array $values
* @param bool $includeGroupColumn
*
* @return array
*/
public function fetchGroup($statement, array $values = [], $includeGroupColumn = false);
/**
* @param string $statement
* @param array $values
*
* @return int|float|string|false false on empty result
*/
public function fetchValue($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchRow($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchCol($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchPairs($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldCol($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAssoc($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAll($statement, array $values = []);
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldPairs($statement, array $values = []);
/**
* Escapes values for "BOOLEAN" and (TINY)INT columns
*
* @param bool|null $value
* @param bool $isNullable
*
* @return string
*/
public function escapeBool($value, $isNullable = false);
/**
* Escapes values for INT columns
*
* @param mixed $value
* @param bool $isNullable
*
* @return string
*/
public function escapeInt($value, $isNullable = false);
/**
* Escapes values for FLOAT, DOUBLE and REAL columns
*
* @param mixed $value
* @param bool $isNullable
*
* @return string
*/
public function escapeDecimal($value, $isNullable = false);
/**
* Escapes values for CHAR, VARCHAR, TEXT and BLOB columns
*
* @param mixed $value
* @param bool $isNullable
*
* @return string
*/
public function escapeString($value, $isNullable = false);
/**
* Escapes and quotes an identifier (column or table name)
*
* @param string $value
*
* @return string
*/
public function escapeIdentifier($value);
}

View File

@ -0,0 +1,883 @@
<?php
namespace Xentral\Components\Database\Adapter;
use Generator;
use mysqli;
use mysqli_result;
use mysqli_stmt;
use Xentral\Components\Database\DatabaseConfig;
use Xentral\Components\Database\Exception\BindParameterException;
use Xentral\Components\Database\Exception\ConnectionException;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Components\Database\Exception\QueryFailureException;
use Xentral\Components\Database\Exception\TransactionException;
use Xentral\Components\Database\Parser\MysqliArrayValueParser;
use Xentral\Components\Database\Parser\MysqliNamedParameterParser;
use Xentral\Components\Database\Profiler\ProfilerInterface;
final class MysqliAdapter implements AdapterInterface
{
/** @var mysqli|null $connection */
private $connection;
/** @var DatabaseConfig $config */
private $config;
/** @var bool $transactionActive */
private $transactionActive = false;
/** @var int|null $reconnectCounter */
private $reconnectCounter;
/** @var int $reconnectLimit */
private $reconnectMaxCount = 5;
/** @var ProfilerInterface|null $profiler */
private $profiler;
/**
* @param DatabaseConfig $config
* @param ProfilerInterface|null $profiler
*/
public function __construct(DatabaseConfig $config, ProfilerInterface $profiler = null)
{
$this->config = $config;
$this->profiler = $profiler;
}
/**
* @throws ConnectionException
*
* @return void
*/
public function connect()
{
if ($this->connection !== null) {
return;
}
if ($this->reconnectCounter === null) {
$this->reconnectCounter = 0;
} else {
$this->reconnectCounter++;
}
if ($this->reconnectCounter >= $this->reconnectMaxCount) {
throw new ConnectionException(sprintf(
'Too many reconnects. Reconnect count: %d (Max allowed %d)',
$this->reconnectCounter,
$this->reconnectMaxCount
));
}
$this->startProfiler(__FUNCTION__);
$connection = new mysqli(
$this->config->getHostname(),
$this->config->getUsername(),
$this->config->getPassword(),
null,
$this->config->getPort()
);
if ($connection->connect_errno > 0) {
throw new ConnectionException(sprintf(
'Database connection to host "%s" failed. Error code #%s. Error message: %s',
$this->config->getHostname(),
$connection->connect_errno,
$connection->connect_error
));
}
$connection->select_db($this->config->getDatabase());
if ($connection->errno > 0) {
throw new ConnectionException(sprintf(
'Database selection failed for database "%s". Error code #%s. Error message: %s',
$this->config->getDatabase(),
$connection->errno,
$connection->error
));
}
// @see https://www.php.net/manual/de/mysqlinfo.concepts.charset.php
if (!$connection->set_charset($this->config->getCharset())) {
throw new ConnectionException(sprintf(
'Failed to set character set "%s". Error: %s',
$this->config->getCharset(),
$connection->error
));
}
if (!$connection->autocommit(true)) {
throw new ConnectionException(sprintf(
'Failed to activate auto commit. Error: %s',
$connection->error
));
}
$this->finishProfiler(null, [
'dbname' => $this->config->getDatabase(),
'host' => $this->config->getHostname(),
'port' => $this->config->getPort(),
]);
$this->connection = $connection;
foreach ($this->config->getQueries() as $query) {
$this->perform($query);
}
}
/**
* @return void
*/
public function disconnect()
{
if ($this->connection === null) {
return;
}
$this->startProfiler(__FUNCTION__);
$this->connection->close();
$this->connection = null;
$this->finishProfiler();
}
/**
* @return bool
*/
public function inTransaction()
{
return $this->transactionActive;
}
/**
* @throws TransactionException If transaction is already started
*
* @return void
*/
public function beginTransaction()
{
if ($this->inTransaction()) {
throw new TransactionException('Transaction is already started.');
}
$this->connect();
if ($this->connection->begin_transaction() === false) {
throw new TransactionException(sprintf('Transaction start failed: %s', $this->connection->error));
}
$this->transactionActive = true;
}
/**
* @throws TransactionException
*
* @return void
*/
public function commit()
{
if (!$this->inTransaction()) {
throw new TransactionException('Transaction not started.');
}
$this->connection->commit();
$this->connection->autocommit(true);
$this->transactionActive = false;
}
/**
* @throws TransactionException
*
* @return void
*/
public function rollback()
{
if (!$this->inTransaction()) {
throw new TransactionException('Transaction not started.');
}
$this->connection->rollback();
$this->connection->autocommit(true);
$this->transactionActive = false;
}
/**
* @return int
*/
public function lastInsertId()
{
return (int)$this->connection->insert_id;
}
/**
* @param array $values
* @param string $statement
*
* @throws QueryFailureException
*
* @return void
*/
public function perform($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$query = $this->getMysqliStatement($statement, $values);
if (!is_object($query)) {
throw new QueryFailureException(sprintf('Database query failed: %s', $this->connection->error));
}
$query->close();
$this->finishProfiler($statement, $values);
}
/**
* @param string $statement
* @param array $values
*
* @throws QueryFailureException
*
* @return int
*/
public function fetchAffected($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$query = $this->getMysqliStatement($statement, $values);
if (!is_object($query)) {
throw new QueryFailureException(sprintf('Database query failed: %s', $this->connection->error));
}
$affectedRows = (int)$query->affected_rows;
$query->close();
$this->finishProfiler($statement, $values);
return $affectedRows;
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAll($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
while ($row = $result->fetch_assoc()) {
$data[] = $row;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchAssoc($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
while ($row = $result->fetch_assoc()) {
$assocKey = reset($row); // Fetch first array value
$data[$assocKey] = $row;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchRow($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
if ($result->num_rows === 0) {
$result->close();
return [];
}
$data = $result->fetch_assoc();
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return int|float|string|false false on empty result
*/
public function fetchValue($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
if ($result->num_rows === 0) {
$result->close();
return false;
}
$data = $result->fetch_assoc();
$result->close();
$this->finishProfiler($statement, $values);
return reset($data);
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function fetchCol($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
while ($row = $result->fetch_assoc()) {
$firstValue = reset($row); // Fetch first array value
$data[] = $firstValue;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @throws QueryFailureException
*
* @return array
*/
public function fetchPairs($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
if ($result->field_count !== 2) {
throw new QueryFailureException('Field count does not match. fetchPairs() allows only two fields.');
}
$data = [];
while ($row = $result->fetch_assoc()) {
$key = array_shift($row);
$value = array_shift($row);
$data[$key] = $value;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
* @param bool $includeGroupColumn
*
* @return array
*/
public function fetchGroup($statement, array $values = [], $includeGroupColumn = false)
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$data = [];
$includeGroupColumn = (bool)$includeGroupColumn;
while ($row = $result->fetch_assoc()) {
$group = $includeGroupColumn === true ? reset($row) : array_shift($row); // Fetch first array value
if (!isset($data[$group])) {
$data[$group] = [];
}
$data[$group][] = $row;
}
$result->close();
$this->finishProfiler($statement, $values);
return $data;
}
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAll($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
while ($row = $result->fetch_assoc()) {
yield $row;
}
$result->close();
}
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldAssoc($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
while ($row = $result->fetch_assoc()) {
$assocKey = reset($row); // Fetch first array value
yield $assocKey => $row;
}
$result->close();
}
/**
* @param string $statement
* @param array $values
*
* @return Generator
*/
public function yieldCol($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
while ($row = $result->fetch_assoc()) {
$firstValue = reset($row); // Fetch first array value
yield $firstValue;
}
$result->close();
}
/**
* @param string $statement
* @param array $values
*
* @throws QueryFailureException
*
* @return Generator
*/
public function yieldPairs($statement, array $values = [])
{
$this->connect();
$this->startProfiler(__FUNCTION__);
$result = $this->getMysqliResult($statement, $values);
$this->finishProfiler($statement, $values);
if ($result->field_count !== 2) {
throw new QueryFailureException('Field count does not match. yieldPairs() allows only two fields.');
}
while ($row = $result->fetch_assoc()) {
$key = array_shift($row);
$value = array_shift($row);
yield $key => $value;
}
$result->close();
}
/**
* Escapes values for "BOOLEAN" and (TINY)INT columns
*
* @param bool|null $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeBool($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_bool($value)) {
throw new EscapingException('Can not escape bool. Value is not a bool.');
}
return $value === true ? '1' : '0';
}
/**
* Escapes values for INT columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeInt($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_int($value)) {
throw new EscapingException('Can not escape integer. Value is not an integer.');
}
return (string)(int)$value;
}
/**
* Escapes values for FLOAT, DOUBLE and REAL columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeDecimal($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_numeric($value)) {
throw new EscapingException('Can not escape decimal. Value is not numeric.');
}
return (string)$value;
}
/**
* Escapes values for CHAR, VARCHAR, TEXT and BLOB columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeString($value, $isNullable = false)
{
if ($isNullable === true && $value === null) {
return 'NULL';
}
if (!is_string($value)) {
throw new EscapingException('Can not escape string. Value is not a string.');
}
$this->connect();
return "'" . $this->connection->real_escape_string($value) . "'";
}
/**
* Escapes and quotes an identifier (column or table name)
*
* @param string $value
*
* @throws EscapingException
*
* @return string
*/
public function escapeIdentifier($value)
{
if (!is_string($value)) {
throw new EscapingException('Can not escape identifier. Passed value is not a string.');
}
if (empty(trim($value))) {
throw new EscapingException('Can not escape identifier. Passed value is empty.');
}
$parts = explode('.', $value);
if (count($parts) > 2) {
throw new EscapingException('Can not escape identifier. Identifier contains more than one dots.');
}
$partsCleaned = [];
foreach ($parts as $part) {
if (empty(trim($part))) {
throw new EscapingException(
'Can not escape identifier. Parts before and after the dot can not be empty.'
);
}
if (strlen($part) > 64) {
throw new EscapingException(
'Can not escape identifier. Identifier is too long. Only 64 characters are allowed.'
);
}
$partCleaned = preg_replace('/[^A-Za-z0-9_]+/', '', $part);
if (strlen($partCleaned) !== strlen($part)) {
throw new EscapingException(
'Can not escape identifier. Passed value contains invalid characters. ' .
'Valid characters: A-Z, a-z, 0-9, Underscore'
);
}
$partsCleaned[] = '`' . $partCleaned . '`';
}
return implode('.', $partsCleaned);
}
/**
* @return void
*/
public function __clone()
{
$this->connection = null;
$this->transactionActive = false;
$this->config = clone $this->config;
}
/**
* @return void
*/
public function __destruct()
{
$this->disconnect();
}
/**
* @return void
*/
public function __wakeup()
{
}
/**
* @return array
*/
public function __sleep()
{
return [];
}
/**
* @param string $statement
* @param array $values
*
* @return mysqli_stmt
*/
private function getMysqliStatement($statement, array $values = [])
{
list($statement, $values) = $this->replaceArrayValues($statement, $values);
list($rebuildStatement, $bindValues, $parameterNames) = $this->replaceNamedParameters($statement, $values);
$query = $this->connection->prepare($rebuildStatement);
if ($query === false && $this->connection->errno === 2006) {
// Code 2006 = MySQL server has gone away
// Falls Verbindung in einen Timeout gelaufen ist
// => Verbindung wiederherstellen und Prepare erneut probieren
$this->disconnect();
$this->connect();
$query = $this->connection->prepare($rebuildStatement);
}
if ($query === false || !is_object($query)) {
throw new QueryFailureException(
sprintf(
'Database prepare failed. Error code #%s. Error message: %s',
$this->connection->errno,
$this->connection->error
),
(int)$this->connection->errno
);
}
$this->bindParametersToMysqliStatement($query, $bindValues, $parameterNames);
$query->execute();
if ($query->errno > 0) {
throw new QueryFailureException(
sprintf('Database query failed. Error code #%s. Error message: %s', $query->errno, $query->error),
(int)$query->errno
);
}
return $query;
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
private function replaceArrayValues($statement, array $values = [])
{
$parser = new MysqliArrayValueParser();
$result = $parser->rebuild($statement, $values);
return [$result['statement'], $result['values']];
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
private function replaceNamedParameters($statement, array $values = [])
{
$parser = new MysqliNamedParameterParser();
$result = $parser->rebuild($statement, $values);
return [$result['statement'], $result['values'], $result['params']];
}
/**
* @param mysqli_stmt $statement
* @param array $bindValues Values for binding
* @param array $parameterNames Original parameter names (for debugging only)
*
* @return void
*/
private function bindParametersToMysqliStatement($statement, $bindValues, $parameterNames)
{
if (empty($bindValues)) {
return;
}
$bindTypes = '';
foreach ($bindValues as $index => &$bindValue) {
if (is_bool($bindValue)) {
$bindValue = (int)$bindValue;
$bindTypes .= 'i'; // integer
continue;
}
if (is_float($bindValue)) {
$bindTypes .= 'd'; // double
continue;
}
if (is_array($bindValue)) {
throw new BindParameterException(sprintf(
'Can not bind parameter of type "array" to placeholder "%s".',
$parameterNames[$index]
));
}
if (is_object($bindValue)) {
throw new BindParameterException(sprintf(
'Can not bind parameter of type "object" to placeholder "%s".',
$parameterNames[$index]
));
}
$bindTypes .= 's'; // string
}
unset($bindValue);
$statement->bind_param($bindTypes, ...$bindValues);
}
/**
* @param $statement
* @param array $values
*
* @return mysqli_result
*/
private function getMysqliResult($statement, array $values = [])
{
$query = $this->getMysqliStatement($statement, $values);
$result = $query->get_result();
$query->close();
if ($result === false) {
throw new QueryFailureException(
sprintf('Database query failed. Error code #%s. Error message: %s', $query->errno, $query->error),
(int)$query->errno
);
}
return $result;
}
/**
* @param string $methodName
*
* @return void
*/
private function startProfiler($methodName)
{
if ($this->profiler === null) {
return;
}
$this->profiler->start(__CLASS__, $methodName);
}
/**
* @param string|null $statement
* @param array $values
*
* @return void
*/
private function finishProfiler($statement = null, array $values = [])
{
if ($this->profiler === null) {
return;
}
$this->profiler->finish($statement, $values);
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace Xentral\Components\Database;
use Xentral\Components\Database\Adapter\MysqliAdapter;
use Xentral\Components\Database\Exception\ConfigException;
use Xentral\Components\Database\Profiler\Profiler;
use Xentral\Components\Database\SqlQuery\QueryFactory;
use Xentral\Components\Logger\Context\ContextHelper;
use Xentral\Components\Logger\MemoryLogger;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Core\DependencyInjection\ServiceContainer;
use Xentral\Core\LegacyConfig\ConfigLoader;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'Database' => 'onInitDatabase',
'DatabaseProfiler' => 'onGetDatabaseProfiler',
'MysqliAdapter' => 'onInitMysqliAdapter',
'QueryFactory' => 'onInitQueryFactory',
];
}
/**
* @param ServiceContainer $container
*
* @return Database
*/
public static function onInitDatabase(ServiceContainer $container)
{
return new Database($container->get('MysqliAdapter'), $container->get('QueryFactory'));
}
/**
* @return QueryFactory
*/
public static function onInitQueryFactory()
{
return new QueryFactory('mysql');
}
/**
* @param ServiceContainer $container
*
* @return Profiler
*/
public static function onGetDatabaseProfiler(ServiceContainer $container)
{
$request = $container->get('Request');
return new Profiler(new MemoryLogger(new ContextHelper($request)));
}
/**
* @param ContainerInterface $container
*
* @return MysqliAdapter
*/
public static function onInitMysqliAdapter(ContainerInterface $container)
{
$conf = ConfigLoader::load();
$dbHost = property_exists($conf, 'WFdbhost') ? $conf->WFdbhost : 'localhost';
$dbPort = property_exists($conf, 'WFdbport') ? $conf->WFdbport : 3306;
$dbName = property_exists($conf, 'WFdbname') ? $conf->WFdbname : null;
$dbUser = property_exists($conf, 'WFdbuser') ? $conf->WFdbuser : null;
$dbPass = property_exists($conf, 'WFdbpass') ? $conf->WFdbpass : null;
if (empty($dbName)) {
throw new ConfigException('Could not connect to database. Database name is missing or empty.');
}
if (empty($dbUser)) {
throw new ConfigException('Could not connect to database. Database user is missing or empty.');
}
if (empty($dbPass)) {
throw new ConfigException('Could not connect to database. Database password is missing or empty.');
}
$startupQueries = [
"SET NAMES 'utf8', " .
"CHARACTER SET 'utf8', " .
"lc_time_names = 'de_DE', " .
"SESSION sql_mode = '', " .
"SESSION sql_big_selects = 1;",
];
$config = new DatabaseConfig($dbHost, $dbUser, $dbPass, $dbName, 'utf8', $dbPort, $startupQueries);
// Profiler aktivieren
// Kann mit $container->get('DatabaseProfiler')->getContexts() abgefragt werden
$profiler = $container->get('DatabaseProfiler');
if (defined('DEVELOPMENT_MODE') && DEVELOPMENT_MODE === true) {
$profiler->setActive(true);
}
return new MysqliAdapter($config, $profiler);
}
}

View File

@ -0,0 +1,356 @@
<?php /** @noinspection PhpInconsistentReturnPointsInspection */
namespace Xentral\Components\Database;
use Generator;
use Xentral\Components\Database\Adapter\AdapterInterface;
use Xentral\Components\Database\Exception\EscapingException;
use Xentral\Components\Database\Exception\TransactionException;
use Xentral\Components\Database\SqlQuery\DeleteQuery;
use Xentral\Components\Database\SqlQuery\InsertQuery;
use Xentral\Components\Database\SqlQuery\SelectQuery;
use Xentral\Components\Database\SqlQuery\QueryFactory;
use Xentral\Components\Database\SqlQuery\UpdateQuery;
final class Database
{
/** @var AdapterInterface $adapter */
private $adapter;
/** @var QueryFactory $queryFactory */
private $queryFactory;
/**
* @param AdapterInterface $adapter
* @param QueryFactory $queryFactory
*/
public function __construct(AdapterInterface $adapter, QueryFactory $queryFactory)
{
$this->adapter = $adapter;
$this->queryFactory = $queryFactory;
}
/**
* Aura.SqlQuery
*/
/**
* @return SelectQuery
*/
public function select()
{
return $this->queryFactory->newSelect();
}
/**
* @return InsertQuery
*/
public function insert()
{
return $this->queryFactory->newInsert();
}
/**
* @return UpdateQuery
*/
public function update()
{
return $this->queryFactory->newUpdate();
}
/**
* @return DeleteQuery
*/
public function delete()
{
return $this->queryFactory->newDelete();
}
/**
* ENDE: Aura.SqlQuery
*/
/**
* Close database connection
*
* @return void
*/
public function close()
{
$this->adapter->disconnect();
}
/**
* Executes simple queries without named parameters
*
* Use self::perform() for queries with named parameters.
*
* @param string $query
*
* @return void
*/
public function exec($query)
{
$this->adapter->perform($query, []);
}
/**
* @return int
*/
public function lastInsertId()
{
return (int)$this->adapter->lastInsertId();
}
/**
* @throws TransactionException If transaction is already started
*
* @return void
*/
public function beginTransaction()
{
$this->adapter->beginTransaction();
}
/**
* @return void
*/
public function commit()
{
$this->adapter->commit();
}
/**
* @return void
*/
public function rollBack()
{
$this->adapter->rollBack();
}
/**
* @return bool
*/
public function inTransaction()
{
return $this->adapter->inTransaction();
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchAll($query, array $values = [])
{
return $this->adapter->fetchAll($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchAssoc($query, array $values = [])
{
return $this->adapter->fetchAssoc($query, $values);
}
/**
* @param string $query
* @param array $values
* @param bool $includeGroupColumn
*
* @return array Empty array on empty result
*/
public function fetchGroup($query, array $values = [], $includeGroupColumn = false)
{
return $this->adapter->fetchGroup($query, $values, (bool)$includeGroupColumn);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchRow($query, array $values = [])
{
return $this->adapter->fetchRow($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchPairs($query, array $values = [])
{
return $this->adapter->fetchPairs($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return int|float|string|false false on empty result
*/
public function fetchValue($query, array $values = [])
{
return $this->adapter->fetchValue($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return int
*/
public function fetchAffected($query, array $values = [])
{
return $this->adapter->fetchAffected($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return array Empty array on empty result
*/
public function fetchCol($query, array $values = [])
{
return $this->adapter->fetchCol($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldAll($query, array $values = [])
{
return $this->adapter->yieldAll($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldAssoc($query, array $values = [])
{
return $this->adapter->yieldAssoc($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldPairs($query, array $values = [])
{
return $this->adapter->yieldPairs($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return Generator
*/
public function yieldCol($query, array $values = [])
{
return $this->adapter->yieldCol($query, $values);
}
/**
* @param string $query
* @param array $values
*
* @return void
*/
public function perform($query, array $values = [])
{
$this->adapter->perform($query, $values);
}
/**
* Escapes values for "BOOLEAN" and (TINY)INT columns
*
* @param bool|null $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeBool($value, $isNullable = false)
{
return $this->adapter->escapeBool($value, $isNullable);
}
/**
* Escapes values for INT columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeInt($value, $isNullable = false)
{
return $this->adapter->escapeInt($value, $isNullable);
}
/**
* Escapes values for FLOAT, DOUBLE and REAL columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeDecimal($value, $isNullable = false)
{
return $this->adapter->escapeDecimal($value, $isNullable);
}
/**
* Escapes values for CHAR, VARCHAR, TEXT and BLOB columns
*
* @param mixed $value
* @param bool $isNullable
*
* @throws EscapingException
*
* @return string
*/
public function escapeString($value, $isNullable = false)
{
return $this->adapter->escapeString($value, $isNullable);
}
/**
* Escapes and quotes an identifier (column or table name)
*
* @param string $value
*
* @throws EscapingException
*
* @return string
*/
public function escapeIdentifier($value)
{
return $this->adapter->escapeIdentifier($value);
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace Xentral\Components\Database;
final class DatabaseConfig
{
/** @var string $hostname */
private $hostname;
/** @var string $username */
private $username;
/** @var string $password */
private $password;
/** @var string $database */
private $database;
/** @var string $charset */
private $charset;
/** @var int $port */
private $port;
/** @var array $queries */
private $queries;
/**
* @param string $hostname
* @param string $username
* @param string $password
* @param string $database
* @param string|null $charset
* @param int|null $port
* @param array $queries
*/
public function __construct(
$hostname,
$username,
$password,
$database,
$charset = null,
$port = null,
array $queries = []
) {
$this->hostname = (string)$hostname;
$this->username = (string)$username;
$this->password = (string)$password;
$this->database = (string)$database;
$this->charset = $charset !== null ? (string)$charset : 'utf8';
$this->port = $port !== null ? (int)$port : 3306;
$this->queries = $queries;
}
/**
* @param array $config
*
* @return DatabaseConfig
*/
public static function fromArray(array $config)
{
return new DatabaseConfig(
$config['hostname'],
$config['username'],
$config['password'],
$config['database'],
isset($config['charset']) ? $config['charset'] : null,
isset($config['port']) ? $config['port'] : null,
isset($config['queries']) ? $config['queries'] : []
);
}
/**
* @return string
*/
public function getHostname()
{
return $this->hostname;
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return string
*/
public function getDatabase()
{
return $this->database;
}
/**
* @return string
*/
public function getCharset()
{
return $this->charset;
}
/**
* @return int
*/
public function getPort()
{
return $this->port;
}
/**
* @return array
*/
public function getQueries()
{
return $this->queries;
}
/**
* @return array
*/
public function __debugInfo()
{
return [
'args' => [
'hostname' => $this->hostname,
'username' => '****',
'password' => '****',
'database' => $this->database,
'charset' => $this->charset,
'port' => $this->port,
'queries' => $this->queries,
],
];
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class BindParameterException extends \RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class ConfigException extends \InvalidArgumentException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class ConnectionException extends \RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace Xentral\Components\Database\Exception;
use RuntimeException;
/**
* @deprecated Will be removed in 19.4
*/
class DatabaseException extends RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface DatabaseExceptionInterface extends ComponentExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class EscapingException extends \RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class MissingParameterException extends \RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class NotConnectedException extends \LogicException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class QueryFailureException extends \RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Xentral\Components\Database\Exception;
class TransactionException extends \RuntimeException implements DatabaseExceptionInterface
{
}

View File

@ -0,0 +1,66 @@
<?php
namespace Xentral\Components\Database\Parser;
/**
* @example
* self::rebuild('SELECT * FROM foo WHERE id IN (:ids)', ['ids' => [1, 2, 3]])
* Erzeugt:
* [
* 'statement' => 'SELECT * FROM foo WHERE id IN (:ids_expl_0_, :ids_expl_1_, :ids_expl_2_)',
* 'values' => [
* '_ids_expl_0_' => 1,
* '_ids_expl_1_' => 2,
* '_ids_expl_2_' => 3,
* ]
* ]
*/
final class MysqliArrayValueParser implements ParserInterface
{
/**
* @param string $statement
* @param array $values
*
* @return array
* - Array key 'statement' contains the rebuild statement
* - Array key 'values' contains the rebuild bind parameters
*/
public function rebuild($statement, array $values = [])
{
return $this->replaceArrayValues($statement, $values);
}
/**
* @param string $statement
* @param array $values
*
* @return array
*/
private function replaceArrayValues($statement, array $values = [])
{
foreach ($values as $paramName => $paramValue) {
if (is_array($paramValue)) {
$counter = 0;
$additionalParams = [];
foreach ($paramValue as $arrayValue) {
$additionalParamName = '_' . $paramName . '_expl_' . $counter . '_';
$additionalParams[] = ':' . $additionalParamName;
$values[$additionalParamName] = $arrayValue;
$counter++;
}
// Replace original named parameter by exploded parameters in statement
$replaceString = implode(', ', $additionalParams);
$statement = str_replace(':' . $paramName, $replaceString, $statement);
// Remove original parameter value
unset($values[$paramName]);
}
}
return [
'statement' => $statement,
'values' => $values,
];
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace Xentral\Components\Database\Parser;
use Xentral\Components\Database\Exception\MissingParameterException;
/**
* Responsibility of this class is to make sql statement and bind values compatible with mysqli
*
* (Mysqli does not support named parameters)
*
* It does this by:
* - Replacing named parameters (:param) by ?-Placeholder (in statement)
* - Rearranging bind values in order of appearance of named parameters
*/
final class MysqliNamedParameterParser implements ParserInterface
{
/**
* @param string $statement
* @param array $values
*
* @return array
*/
public function rebuild($statement, array $values = [])
{
return $this->replaceNamedParameters($statement, $values);
}
/**
* @param string $statement
* @param array $values
*
* @throws MissingParameterException
*
* @return array
*/
private function replaceNamedParameters($statement, array $values = [])
{
$result = [
'statement' => $statement,
'values' => [],
'params' => [],
];
if (empty($values)) {
return $result;
}
// Split statement on named parameters
$parts = preg_split('/(:[a-zA-Z0-9_]+)/um', $statement, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach ($parts as &$part) {
if (strpos($part, ':') !== 0) {
continue; // SQL part does not contain named parameter
}
$parameterName = substr_replace($part, '', 0, 1);
if (!array_key_exists($parameterName, $values)) {
throw new MissingParameterException(sprintf(
'Parameter "%s" is missing from the bound values',
$parameterName
));
}
// Push values in same order of parameters for binding
$result['values'][] = $values[$parameterName];
$result['params'][] = $parameterName; // For debugging only
// Replace named parameter by ?-Placeholder
$part = '?';
}
unset($part);
// Rebuild statement from (changed) parts
$result['statement'] = implode('', $parts);
return $result;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Xentral\Components\Database\Parser;
interface ParserInterface
{
/**
* @param string $statement
* @param array $values
*
* @return array
* - Array key 'statement' contains the corrected statement
* - Array key 'values' contains the corrected bind values
*/
public function rebuild($statement, array $values = []);
}

View File

@ -0,0 +1,135 @@
<?php
namespace Xentral\Components\Database\Profiler;
use Exception;
use Xentral\Components\Logger\LoggerInterface;
final class Profiler implements ProfilerInterface
{
/** @var LoggerInterface $logger */
private $logger;
/** @var bool $active */
private $active = false;
/** @var string $logLevel */
private $logLevel = 'debug';
/** @var string $logFormat */
private $logFormat = "{method} ({duration}): {statement} \n{backtrace}";
/** @var array $context */
private $context = [];
/** @var array $contexts */
private $contexts = [];
/**
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* @param string $className
* @param string $methodName
*
* @return void
*/
public function start($className, $methodName)
{
if (!$this->active) {
return;
}
$this->context = [
'class' => $className,
'method' => $methodName,
'start' => microtime(true),
];
}
/**
* @param string|null $statement
* @param array $values
*
* @return void
*/
public function finish($statement = null, array $values = [])
{
if (!$this->active) {
return;
}
$finish = microtime(true);
$exception = new Exception();
$this->context['finish'] = $finish;
$this->context['duration_real'] = $finish - $this->context['start'];
$this->context['duration'] = sprintf('%.6f', $this->context['duration_real']) . ' seconds';
$this->context['statement'] = $statement;
$this->context['bindings'] = $values;
$this->context['backtrace'] = $exception->getTraceAsString();
$this->logger->log($this->logLevel, $this->logFormat, $this->context);
$this->contexts[] = $this->context;
$this->context = [];
}
/**
* @return bool
*/
public function isActive()
{
return $this->active;
}
/**
* @param bool $active
*
* @return void
*/
public function setActive($active)
{
$this->active = (bool)$active;
}
/**
* /**
* @return string
*/
public function getLogLevel()
{
return $this->logLevel;
}
/**
* @param string $logLevel
*
* @return void
*/
public function setLogLevel($logLevel)
{
$this->logLevel = (string)$logLevel;
}
/**
* @return LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* @return array
*/
public function getContexts()
{
return $this->contexts;
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Xentral\Components\Database\Profiler;
use Xentral\Components\Logger\LoggerInterface;
interface ProfilerInterface
{
/**
* @param string $className
* @param string $methodName
*
* @return void
*/
public function start($className, $methodName);
/**
* @param string|null $statement
* @param array $values
*
* @return void
*/
public function finish($statement = null, array $values = []);
/**
* @return bool
*/
public function isActive();
/**
* @param bool $active
*
* @return void
*/
public function setActive($active);
/**
/**
* @return string
*/
public function getLogLevel();
/**
* @param string $logLevel
*
* @return void
*/
public function setLogLevel($logLevel);
/**
* @return LoggerInterface
*/
public function getLogger();
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Delete;
final class DeleteQuery extends Delete
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Insert;
final class InsertQuery extends Insert
{
}

View File

@ -0,0 +1,59 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\AbstractQuery;
use Aura\SqlQuery\QueryFactory as AuraQueryFactory;
final class QueryFactory extends AuraQueryFactory
{
/**
* @return SelectQuery
*/
public function newSelect()
{
return $this->newInstance('Select');
}
/**
* @return InsertQuery
*/
public function newInsert()
{
$insert = $this->newInstance('Insert');
$insert->setLastInsertIdNames($this->last_insert_id_names);
return $insert;
}
/**
* @return UpdateQuery
*/
public function newUpdate()
{
return $this->newInstance('Update');
}
/**
* @return DeleteQuery
*/
public function newDelete()
{
return $this->newInstance('Delete');
}
/**
* @param string $query The query object type.
*
* @return AbstractQuery
*/
protected function newInstance($query)
{
$class = "Xentral\\Components\\Database\\SqlQuery\\{$query}Query";
return new $class(
$this->getQuoter(),
$this->newSeqBindPrefix()
);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Select;
use Closure;
final class SelectQuery extends Select
{
/**
* @return bool
*/
public function hasOrderBy()
{
return !empty($this->order_by);
}
/**
* @param string $andor
* @param array|callable $args
*
* @return Select
*/
protected function addWhere($andor, $args)
{
if ($args[0] instanceof Closure) {
$this->addClauseCondClosure('where', $andor, $args[0]);
return $this;
}
return parent::addWhere($andor, $args);
}
/**
* Aura.SqlQuery 2.x unterstützt keine Klammersetzung in WHERE-Bedingungen
*
* @see https://github.com/auraphp/Aura.SqlQuery/issues/97
*
* Feature aus Version 3 importiert: https://github.com/auraphp/Aura.SqlQuery/pull/136/files
*
* @copyright Paul M. Jones
* @license MIT
*
* @param string $clause
* @param string $andor
* @param callable $closure
*/
protected function addClauseCondClosure($clause, $andor, $closure)
{
// retain the prior set of conditions, and temporarily reset the clause
// for the closure to work with (otherwise there will be an extraneous
// opening AND/OR keyword)
$set = $this->$clause;
$this->$clause = [];
// invoke the closure, which will re-populate the $this->$clause
$closure($this);
// are there new clause elements?
if (!$this->$clause) {
// no: restore the old ones, and done
$this->$clause = $set;
return;
}
// append an opening parenthesis to the prior set of conditions,
// with AND/OR as needed ...
if ($set) {
$set[] = "{$andor} (";
} else {
$set[] = "(";
}
// append the new conditions to the set, with indenting
foreach ($this->$clause as $cond) {
$set[] = " {$cond}";
}
$set[] = ")";
// ... then put the full set of conditions back into $this->$clause
$this->$clause = $set;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Database\SqlQuery;
use Aura\SqlQuery\Mysql\Update;
final class UpdateQuery extends Update
{
}

View File

@ -0,0 +1,77 @@
# Data Manipulation
## `perform()`
Für alle SQL Anweisung außer `SELECT`. Die Methode gibt nichts zurück. Sollte die Ausführung
fehlschlagen, so wird eine Exception geworfen.
```php
$sql = 'UPDATE foo SET bar = :bar WHERE id = :id';
$values = [
'bar' => 'baz',
'id' => 123,
];
$db->perform($sql, $values);
```
## `fetchAffected()`
Für `INSERT`, `REPLACE`, `UPDATE` und `DELETE` Anweisungen. Gibt die Anzahl der betroffenen Datensätze zurück.
```php
$sql = 'UPDATE foo SET bar = :bar WHERE 1';
$values = [
'bar' => 'baz',
];
echo $db->fetchAffected($sql, $values);
```
###### Ausgabe
```
42
```
## `lastInsertId()`
Gibt den zuletzt erzeugten Auto-Increment-Wert zurück.
```php
$sql = 'INSERT INTO foo (id, bar) VALUES (NULL, :bar)';
$values = [
'bar' => 'baz',
];
$db->perform($sql, $values);
// $db->fetchAffected($sql, $values); // Alternative
echo $db->lastInsertId();
```
###### Ausgabe
```
123
```
## Multiple-Row-Insert
```php
$sql = 'INSERT INTO foo (id, bar) VALUES (NULL, :bar1, :baz1), (NULL, :bar2, :baz2), (NULL, :bar3, :baz3)';
$values = [
'bar1' => 'bar',
'baz1' => 'baz',
'bar2' => 'barbar',
'baz2' => 'bazbaz',
'bar3' => 'barbarbar',
'baz3' => 'bazbazbaz',
];
$db->perform($sql, $values);
// $db->fetchAffected($sql, $values); // Alternative; Rückgabe wäre `3`
```

View File

@ -0,0 +1,158 @@
# Datensätze abrufen
## `fetchAll()`
```php
$data = $db->fetchAll('SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.id > 5');
```
```
array (
0 =>
array (
'id' => 6,
'typ' => 'produkt',
'name_de' => 'LED Anzeige RLED 24-8',
),
1 =>
array (
'id' => 7,
'typ' => 'produkt',
'name_de' => 'Schalter S3 24V 5A',
),
...
)
```
## `fetchAssoc()`
Wie `fetchAll()` allerdings wird das Ergebnis der ersten Spalte als Index verwendet.
```php
$data = $db->fetchAssoc('SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.id > 5');
```
```
array (
6 =>
array (
'id' => 6,
'typ' => 'produkt',
'name_de' => 'LED Anzeige RLED 24-8',
),
7 =>
array (
'id' => 7,
'typ' => 'produkt',
'name_de' => 'Schalter S3 24V 5A',
),
...
)
```
## `fetchRow()`
Liefert die Ergebnisse der ersten Zeile als assoziatives Array. Die Spaltennamen werden als Index verwendet.
```php
$data = $db->fetchRow('SELECT a.id, a.name_de, a.name_en, a.logdatei FROM artikel AS a WHERE a.id > 5');
```
```
array (
'id' => 6,
'name_de' => 'LED Anzeige RLED 24-8',
'name_en' => '',
'logdatei' => '2015-10-26 17:26:27',
)
```
## `fetchValue()`
Liefert nur das Ergebnis der ersten Zeile und Spalte zurück.
```php
$data = $db->fetchValue('SELECT a.name_de, name_en, a.logdatei FROM artikel AS a WHERE a.id > 5');
```
```
'LED Anzeige RLED 24-8'
```
## `fetchCol()`
Liefert nur die Ergebnisse der ersten Spalte zurück.
```php
$data = $db->fetchCol('SELECT a.name_de, a.name_en, a.logdatei FROM artikel AS a WHERE a.id > 5');
```
```
array (
0 => 'LED Anzeige RLED 24-8',
1 => 'Schalter S3 24V 5A',
2 => 'Gehäuse GHK5 20x30x10',
...
)
```
## `fetchPairs()`
Gibt ein (eindimentionales) assoziatives Array zurück. Der Wert der ersten Spalte wird als Index verwendet und die zweite Spalte als Wert.
**Erwartet werden genau zwei Spalten, andernfalls wird eine Exception geworfen.**
```php
$data = $db->fetchPairs('SELECT a.nummer, a.name_de FROM artikel AS a WHERE a.id > 5');
```
```
array (
700006 => 'LED Anzeige RLED 24-8',
700007 => 'Schalter S3 24V 5A',
700008 => 'Gehäuse GHK5 20x30x10',
...
)
```
## `fetchGroup()`
Verhält sich wie `fetchAssoc()`; mit der Ausnahme dass die Werte der ersten Spalte gruppiert werden.
```php
$data = $db->fetchGroup(
'SELECT a.typ, a.nummer, a.name_de, a.logdatei FROM artikel AS a WHERE a.id > 5'
);
```
```
array (
'produkt' =>
array (
0 =>
array (
'typ' => 'produkt',
'nummer' => '700006',
'name_de' => 'LED Anzeige RLED 24-8',
'logdatei' => '2015-10-26 17:26:27',
),
1 =>
array (
'typ' => 'produkt',
'nummer' => '700007',
'name_de' => 'Schalter S3 24V 5A',
'logdatei' => '2015-10-26 17:26:59',
),
2 =>
array (
'typ' => 'produkt',
'nummer' => '700008',
'name_de' => 'Gehäuse GHK5 20x30x10',
'logdatei' => '2018-05-23 05:18:52',
),
),
'gebuehr' =>
array (
0 =>
array (
'typ' => 'gebuehr',
'nummer' => '100001',
'name_de' => 'Versandkosten',
'logdatei' => '2018-06-17 10:09:36',
),
),
)
```

View File

@ -0,0 +1,51 @@
# Database-Komponente
###### Verwendete Libraries
* **Aura.SqlQuery**:
* Composer: `aura/sqlquery:2.7.*`
* Packagist: https://packagist.org/packages/aura/sqlquery
* GitHub: https://github.com/auraphp/Aura.SqlQuery
* Docs: https://github.com/auraphp/Aura.SqlQuery/tree/2.x
* **~~Aura.Sql~~** Wird in 19.4 entfernt
* Composer: `aura/sql:3.*`
* Packagist: https://packagist.org/packages/aura/sql
* GitHub: https://github.com/auraphp/Aura.Sql
* Docs: https://github.com/auraphp/Aura.Sql/blob/3.x/docs/index.md
##### Database-Komponente aus Container holen
```php
$db = $container->get('Database');
```
Im alten Bereich:
```php
$db = $this->app->Container->get('Database');
```
## Themen
* [Datensätze abrufen (fetch)](fetch_results.md)
* [Datensätze abrufen mit Generatoren (yield)](yield_results.md)
* [Datensätze ändern](data_manipulation.md)
* [Transaktionen](transactions.md)
* [Named Parameter / Prepared Statements](named_parameter.md)
* [SQL Query Builder](query_builder.md)
## Exceptions
Die Database-Komponente verwendet intern `mysqli`. Im Unterschied zu `mysqli` werden in Fehlerfällen aber
Exceptions geworfen; z.B.:
* Wenn die Verbindung zur Datenbank fehlschlägt => `ConnectionException`
* Wenn ein SQL-Statement fehlerhaft ist oder aus anderen Gründen nicht erfolgreich ausgeführt
werden kann => `QueryFailureException`
* Wenn `Named Parameter` fehlen => `MissingParameterException`
##### ExceptionInterface
Alle Exceptions die von der Database-Komponente geworfen werden implementieren das
`\Xentral\Components\Database\Exception\DatabaseExceptionInterface` Interface.

View File

@ -0,0 +1,44 @@
# Named Parameter
`perform()`, `fetch*()` und `yield*()`-Methoden nehmen als zweiten Parameter ein assoziatives Array entgegen.
Mit diesem Array werden lassen sich Werte als *Named Parameter* 'binden'.
```php
$sql =
'SELECT a.id, a.nummer, a.name_de
FROM artikel AS a
WHERE a.typ IN (:types)
AND a.nummer LIKE :nummers
LIMIT :start, :length';
$values = [
'types' => ['produkt', 'gebuehr'],
'nummers' => '7000%',
'start' => 0,
'length' => 3,
];
$data = $db->fetchAll($sql, $values);
```
```
array (
0 =>
array (
'id' => 1,
'nummer' => '700001',
'name_de' => 'Schraube M10x20',
),
1 =>
array (
'id' => 2,
'nummer' => '700002',
'name_de' => 'Sechskant-Mutter M10',
),
2 =>
array (
'id' => 3,
'nummer' => '700003',
'name_de' => 'Schalthebel 20x10',
),
)
```

View File

@ -0,0 +1,13 @@
# Profiler
```php
<?php
/** @var \Xentral\Components\Database\Profiler\Profiler $profiler */
$profiler = $container->get('DatabaseProfiler');
$profiler->setActive(true);
$database->fetchAll($sql);
var_dump($profiler->getContexts());
```

View File

@ -0,0 +1,97 @@
# SQL Query Builder
###### Verwendete Libraries
* **Aura.SqlQuery**:
* Composer: `aura/sqlquery:2.7.*`
* Packagist: https://packagist.org/packages/aura/sqlquery
* GitHub: https://github.com/auraphp/Aura.SqlQuery
* Docs: https://github.com/auraphp/Aura.SqlQuery/tree/2.x
## Query Builder erzeugen
```php
$select = $db->select();
$update = $db->update();
$insert = $db->insert();
$delete = $db->delete();
```
## SELECT-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#select
###### Beispiel mit Named-Placeholder
```php
$select = $db->select();
$select
->cols(['u.id', 'u.description'])
->from('user AS u')
->where('u.id = :user_id')
->bindValue('user_id', 1);
$result = $db->fetchAll(
$select->getStatement(),
$select->getBindValues()
);
var_export($result);
// array(
// 0 => array(
// 'id' => 1,
// 'description' => 'Administrator',
// ),
// )
```
###### Alternative mit ?-Placeholder
```php
$select = $db->select();
$select
->cols(['u.id', 'u.description'])
->from('user AS u')
->where('u.id = ?', 1);
```
###### Beispiel mit Verschachtelung im WHERE
```php
$select = $db->select();
$select
->cols(['u.id', 'u.description'])
->from('user AS u')
->where('u.activ = ?', 1)
->where(function (SelectQuery $query) {
$query->where('u.type = ?', 'admin')
->orWhere('u.type = ?', 'standard');
});
echo $select->getStatement();
```
```sql
SELECT
`u`.`id`,
`u`.`description`
FROM
`user` AS `u`
WHERE
`u`.`activ` = :_1_1_
AND (
`u`.`type` = :_1_2_
OR `u`.`type` = :_1_3_
)
```
## INSERT-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#insert
## UPDATE-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#update
## DELETE-Query
https://github.com/auraphp/Aura.SqlQuery/tree/2.x#delete

View File

@ -0,0 +1,25 @@
# Transaktionnen
## Transaktion starten
```php
$db->beginTransaction();
```
## Transaktion übernehmen / Commit
```php
$db->commit();
```
## Transaktion zurücknehmen / Rollback
```php
$db->rollback();
```
## Prüfen ob Transaktion gestartet ist
```php
$db->inTransaction();
```

View File

@ -0,0 +1,107 @@
# Datensätze abrufen mit Generatoren
Um den Arbeitsspeicherverbrauch gering zu halten bieten sich zum Iterieren von großen Datenmengen
Generatoren an: https://www.php.net/manual/de/language.generators.overview.php
Die Database-Komponente stellt für diesen Zweck `yield`-Methoden zur Verfügung.
## `yieldAll()`
Wie `fetchAll()`; jede Zeile ist ein assoziatives Array.
```php
$statement = 'SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldAll($statement, $bindValues) as $row) {
var_dump($row);
}
```
```
array (size=3)
'id' => int 2
'typ' => string 'produkt' (length=7)
'name_de' => string 'Sechskant-Mutter M10' (length=20)
array (size=3)
'id' => int 3
'typ' => string 'produkt' (length=7)
'name_de' => string 'Schalthebel 20x10' (length=17)
```
## `yieldAssoc()`
Wie `fetchAssoc()`; jede Zeile ist ein assoziatives Array; der Key beinhaltet den Wert der ersten Spalte
```php
$statement = 'SELECT a.id, a.typ, a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldAssoc($statement, $bindValues) as $key => $row) {
var_dump($key);
var_dump($row);
}
```
```
int 2
array (size=3)
'id' => int 2
'typ' => string 'produkt' (length=7)
'name_de' => string 'Sechskant-Mutter M10' (length=20)
int 3
array (size=3)
'id' => int 3
'typ' => string 'produkt' (length=7)
'name_de' => string 'Schalthebel 20x10' (length=17)
```
## `yieldPairs()`
Wie `fetchPairs()`; jede Zeile besteht aus Key-Value-Paaren; der Key beinhaltet den Inhalt der ersten Spalte;
der Wert den Inhalt der zweiten Spalte.
**Erwartet werden genau zwei Spalten, andernfalls wird eine Exception geworfen.**
```php
$statement = 'SELECT a.id, a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldPairs($statement, $bindValues) as $key => $value) {
var_dump($key);
var_dump($value);
}
```
```
int 2
string 'Sechskant-Mutter M10' (length=20)
int 3
string 'Schalthebel 20x10' (length=17)
```
## `yieldCol()`
Wie `fetchCol()`; jede Zeile beinhaltet nur den Wert der ersten Spalte.
```php
$statement = 'SELECT a.name_de FROM artikel AS a WHERE a.typ = :typ LIMIT 2';
$bindValues = ['typ' => 'produkt'];
foreach ($db->yieldCol($statement, $bindValues) as $key => $value) {
var_dump($key);
var_dump($value);
}
```
```
int 0
string 'Sechskant-Mutter M10' (length=20)
int 1
string 'Schalthebel 20x10' (length=17)
```

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Xentral\Components\EnvironmentConfig;
use SplFileInfo;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Config;
use License;
use Xentral\Modules\Api\LegacyBridge\LegacyApplication;
class Bootstrap
{
/**
* @return array
*/
public static function registerServices(): array
{
return [
'EnvironmentConfig' => 'onInitEnvironmentConfig',
];
}
/**
* @param ContainerInterface $container
*
* @return EnvironmentConfig
*/
public static function onInitEnvironmentConfig(ContainerInterface $container): EnvironmentConfig
{
$provider = self::onInitEnvironmentConfigProvider($container);
return $provider->createEnvironmentConfig();
}
/**
* @param ContainerInterface $container
*
* @return EnvironmentConfigProvider
*/
private static function onInitEnvironmentConfigProvider(ContainerInterface $container): EnvironmentConfigProvider
{
/** @var LegacyApplication $app */
$app = $container->get('LegacyApplication');
if (!class_exists('License')) {
$test = dirname(__DIR__, 3) . '/phpwf/plugins/class.license.php';
$file = new SplFileInfo($test);
if ($file->isFile()) {
include $test;
}
}
return new EnvironmentConfigProvider(new License(), $app->Conf);
}
}

View File

@ -0,0 +1,233 @@
<?php
namespace Xentral\Components\EnvironmentConfig;
final class EnvironmentConfig
{
/**
* @var string $databaseHost
*/
private $databaseHost;
/**
* @var string $databaseName
*/
private $databaseName;
/**
* @var string $databaseUser
*/
private $databaseUser;
/**
* @var string $databasePassword
*/
private $databasePassword;
/**
* @var int $databasePort
*/
private $databasePort;
/**
* @var string $userdataDirectoryPath
*/
private $userdataDirectoryPath;
/**
* @var array|null $ioncubeSystemInformation
*/
private $ioncubeSystemInformation;
/**
* @param string $databaseHost
* @param string $databaseName
* @param string $databaseUser
* @param string $databasePassword
* @param int $databasePort
* @param string $userdataDirectoryPath
* @param array $ioncubeSystemInformation
*/
public function __construct(
string $databaseHost,
string $databaseName,
string $databaseUser,
string $databasePassword,
int $databasePort,
string $userdataDirectoryPath,
?array $ioncubeSystemInformation
) {
$this->databaseHost = $databaseHost;
$this->databaseName = $databaseName;
$this->databaseUser = $databaseUser;
$this->databasePassword = $databasePassword;
$this->databasePort = $databasePort;
$this->userdataDirectoryPath = $userdataDirectoryPath;
$this->ioncubeSystemInformation = $ioncubeSystemInformation;
}
/**
* @return string
*/
public function getDatabaseHost(): string
{
return $this->databaseHost;
}
/**
* @return string
*/
public function getDatabaseName(): string
{
return $this->databaseName;
}
/**
* @return string
*/
public function getDatabaseUser(): string
{
return $this->databaseUser;
}
/**
* @return string
*/
public function getDatabasePassword(): string
{
return $this->databasePassword;
}
/**
* @return int
*/
public function getDatabasePort(): int
{
return $this->databasePort;
}
/**
* @return string
*/
public function getUserdataDirectoryPath(): string
{
return $this->userdataDirectoryPath;
}
/**
* @return ?array
*/
public function getIoncubeSystemInformation(): ?array
{
return $this->ioncubeSystemInformation;
}
/**
* @return bool
*/
public function isSystemHostedOnCloud(): bool
{
return !empty($this->ioncubeSystemInformation['iscloud']['value']);
}
/**
* @return bool
*/
public function isSystemFlaggedAsDevelopmentVersion(): bool
{
return !empty($this->ioncubeSystemInformation['isdevelopmentversion']['value']);
}
/**
* @return bool
*/
public function isSystemFlaggedAsTestVersion(): bool
{
return !empty($this->ioncubeSystemInformation['testlizenz']['value']);
}
/**
* @return int
*/
public function getMaxUser(): int
{
if (!isset($this->ioncubeSystemInformation['maxuser']['value'])) {
return 0;
}
return (int)$this->ioncubeSystemInformation['maxuser']['value'];
}
/**
* @return int
*/
public function getMaxLightUser(): int
{
if (!isset($this->ioncubeSystemInformation['maxlightuser']['value'])) {
return 0;
}
return (int)$this->ioncubeSystemInformation['maxlightuser']['value'];
}
/**
* @return int|null
*/
public function getExpirationTimeStamp(): ?int
{
if (!isset($this->ioncubeSystemInformation['expdate']['value'])) {
return 0;
}
return (int)$this->ioncubeSystemInformation['expdate']['value'];
}
/**
* @param string $name
*
* @return string|null
*/
public function getValueOfSpecificIoncubeSystemInformation(string $name): ?string
{
if ($this->ioncubeSystemInformation === null) {
return null;
}
if (array_key_exists($name, $this->ioncubeSystemInformation)) {
return $this->ioncubeSystemInformation[$name]['value'];
}
return null;
}
/**
* @return array
*/
public function getSystemFallbackEmailAddresses(): array
{
$emailAddresses = [];
$mailAddressSelfBuyCustomer = (string)$this->getValueOfSpecificIoncubeSystemInformation('buyemail');
if ($mailAddressSelfBuyCustomer !== '') {
$emailAddresses[] = $mailAddressSelfBuyCustomer;
}
$mailAddressCustomerLicence = (string)$this->getValueOfSpecificIoncubeSystemInformation('emaillicence');
if ($mailAddressCustomerLicence !== ''
&& strpos($mailAddressCustomerLicence, '@') !== false
&& strpos($mailAddressCustomerLicence, '@xentral.com') === false
&& strpos($mailAddressCustomerLicence, '@xentral.biz') === false) {
$emailAddresses[] = $mailAddressCustomerLicence;
}
//in old licences email-address of customer can be insert in name instead email
$nameCustomerLicence = (string)$this->getValueOfSpecificIoncubeSystemInformation('namelicence');
if ($nameCustomerLicence !== ''
&& strpos($nameCustomerLicence, '@') !== false
&& strpos($nameCustomerLicence, '@xentral.com') === false
&& strpos($nameCustomerLicence, '@xentral.biz') === false) {
$emailAddresses[] = $nameCustomerLicence;
}
return $emailAddresses;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Xentral\Components\EnvironmentConfig;
use Config;
use License;
final class EnvironmentConfigProvider
{
/** @var License $license */
private $license;
/** @var Config $config */
private $config;
/**
* @param License $license
* @param Config $config
*/
public function __construct(License $license, Config $config)
{
$this->license = $license;
$this->config = $config;
}
/**
* @return EnvironmentConfig
*/
public function createEnvironmentConfig(): EnvironmentConfig
{
$environmentConfig = new EnvironmentConfig(
$this->config->WFdbhost, $this->config->WFdbname, $this->config->WFdbuser,
$this->config->WFdbpass, $this->config->WFdbport, $this->config->WFuserdata,
(array)$this->license->getProperties()
);
return $environmentConfig;
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace Xentral\Components\Exporter\Collection;
use ArrayObject;
use AppendIterator;
use Iterator;
use IteratorAggregate;
use Xentral\Components\Exporter\Exception\InvalidArgumentException;
final class DataCollection implements Iterator
{
/** @var AppendIterator $data */
private $data;
/**
* @param array|Iterator $data
*
* @throws InvalidArgumentException
*/
public function __construct(...$data)
{
$this->data = new AppendIterator();
foreach ($data as $item) {
$this->append($item);
}
}
/**
* @param array|Iterator|IteratorAggregate $data
*
* @throws InvalidArgumentException
*/
public function append($data)
{
$type = gettype($data);
if ($type === 'object') {
$type = get_class($data);
if ($data instanceof Iterator) {
$type = 'Iterator';
}
if ($data instanceof IteratorAggregate) {
$type = 'IteratorAggregate';
}
}
switch ($type) {
case 'array':
$this->data->append((new ArrayObject($data))->getIterator());
break;
case 'Iterator':
$this->data->append($data);
break;
case 'IteratorAggregate':
$this->data->append($data->getIterator());
break;
default:
throw new InvalidArgumentException(sprintf('Unsupported type "%s".', $type));
break;
}
}
/**
* Return the current element
*
* @return mixed Can return any type.
*/
public function current()
{
return $this->data->current();
}
/**
* Move forward to next element
*
* @return void
*/
public function next()
{
$this->data->next();
}
/**
* Return the key of the current element
*
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->data->key();
}
/**
* Checks if current position is valid
*
* @return boolean Returns true on success or false on failure.
*/
public function valid()
{
return $this->data->valid();
}
/**
* Rewind the Iterator to the first element
*
* @return void
*/
public function rewind()
{
$this->data->rewind();
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace Xentral\Components\Exporter\Collection;
use ArrayObject;
use Closure;
use Iterator;
use IteratorAggregate;
use Xentral\Components\Exporter\Exception\InvalidArgumentException;
use Xentral\Components\Exporter\Exception\InvalidReturnTypeException;
final class FormatterCollection implements Iterator
{
/** @var Iterator $data */
private $data;
/** @var callable $callback */
private $callback;
/**
* @param array|Iterator $data
* @param callable|Closure $callback
*
* @throws InvalidArgumentException
*/
public function __construct($data, $callback)
{
if (!is_callable($callback, false)) {
throw new InvalidArgumentException('Callback is not callable');
}
$this->callback = $callback;
$type = gettype($data);
if ($type === 'object') {
$type = get_class($data);
if ($data instanceof Iterator) {
$type = 'Iterator';
}
if ($data instanceof IteratorAggregate) {
$type = 'IteratorAggregate';
}
}
switch ($type) {
case 'array':
$this->data = (new ArrayObject($data))->getIterator();
break;
case 'Iterator':
$this->data = $data;
break;
case 'IteratorAggregate':
$this->data = $data->getIterator();
break;
default:
throw new InvalidArgumentException(sprintf('Unsupported type "%s".', $type));
break;
}
}
/**
* Return the current element
*
* @throws InvalidReturnTypeException
*
* @return mixed Can return any type.
*/
public function current()
{
$result = call_user_func($this->callback, $this->data->current());
if (!is_array($result)) {
throw new InvalidReturnTypeException('Formatter return type is invalid . Callable must return an array.');
}
return $result;
}
/**
* Move forward to next element
*
* @return void
*/
public function next()
{
$this->data->next();
}
/**
* Return the key of the current element
*
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->data->key();
}
/**
* Checks if current position is valid
*
* @return boolean Returns true on success or false on failure.
*/
public function valid()
{
return $this->data->valid();
}
/**
* Rewind the Iterator to the first element
*
* @return void
*/
public function rewind()
{
$this->data->rewind();
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace Xentral\Components\Exporter\Csv;
final class CsvConfig
{
/** @var string $delimiter */
private $delimiter;
/** @var string $enclosure */
private $enclosure;
/** @var string $escapeChar */
private $escapeChar;
/** @var string $sourceCharset */
private $sourceCharset;
/** @var string $targetCharset */
private $targetCharset;
/** @var bool $forceEnclosureEnabled */
private $forceEnclosureEnabled;
/**
* @param string $delimiter
* @param string $enclosure
* @param string $escapeChar
* @param string $targetCharset
* @param string $sourceCharset
* @param bool $forceEnclosureEnabled
*/
public function __construct(
$delimiter = ',',
$enclosure = '"',
$escapeChar = "\\",
$targetCharset = 'UTF-8',
$sourceCharset = 'UTF-8',
$forceEnclosureEnabled = false
) {
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
$this->escapeChar = $escapeChar;
$this->targetCharset = $targetCharset;
$this->sourceCharset = $sourceCharset;
$this->forceEnclosureEnabled = $forceEnclosureEnabled;
}
/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}
/**
* @return string
*/
public function getEscapeChar()
{
return $this->escapeChar;
}
/**
* @return string
*/
public function getSourceCharset()
{
return $this->sourceCharset;
}
/**
* @return string
*/
public function getTargetCharset()
{
return $this->targetCharset;
}
/**
* @param string $delimiter
*/
public function setDelimiter($delimiter)
{
$this->delimiter = $delimiter;
}
/**
* @param string $enclosure
*/
public function setEnclosure($enclosure)
{
$this->enclosure = $enclosure;
}
/**
* @param string $escapeChar
*/
public function setEscapeChar($escapeChar)
{
$this->escapeChar = $escapeChar;
}
/**
* @param string $sourceCharset
*/
public function setSourceCharset($sourceCharset)
{
$this->sourceCharset = $sourceCharset;
}
/**
* @param string $targetCharset
*/
public function setTargetCharset($targetCharset)
{
$this->targetCharset = $targetCharset;
}
/**
* @return bool
*/
public function isForceEnclosureEnabled()
{
return $this->forceEnclosureEnabled;
}
/**
* @param bool $forceEnclosureEnabled
*/
public function setForceEnclosureEnabled($forceEnclosureEnabled)
{
$this->forceEnclosureEnabled = $forceEnclosureEnabled;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Xentral\Components\Exporter\Csv;
use Iterator;
use Xentral\Components\Exporter\Exception\FileExistsException;
use Xentral\Components\Exporter\Exception\InvalidResourceException;
final class CsvExporter
{
/** @var CsvConfig $config */
private $config;
/**
* @param CsvConfig|null $config
*/
public function __construct(CsvConfig $config = null)
{
if ($config === null) {
$config = new CsvConfig();
}
$this->config = $config;
}
/**
* @param string $filePath Resource used for writing
* @param array|Iterator $data Multi-dimentional array, Generator or Iterator
*
* @throws FileExistsException|InvalidResourceException
*
* @return void
*/
public function export($filePath, $data)
{
$resource = $this->exportToResource($filePath, $data);
fclose($resource);
}
/**
* Same as ::export() beside that the created resource will be returned
*
* @param string $filePath Resource used for writing
* @param array|Iterator $data Multi-dimentional array, Generator or Iterator
*
* @throws FileExistsException|InvalidResourceException
*
* @return resource
*/
public function exportToResource($filePath, $data)
{
if (is_file($filePath)) {
throw new FileExistsException(sprintf('File creation failed. File "%s" already exists.', $filePath));
}
// 'x+' = Create and open for reading and writing.
// File pointer will be placed at the beginning of the file.
// If the file already exists `fopen` will return false.
// 'b' = Enable binary mode
$resource = @fopen($filePath, 'x+b');
if ($resource === false) {
throw new InvalidResourceException(sprintf('Failed to open resource for file path "%s".', $filePath));
}
$writer = new CsvWriter($resource, $this->config);
$writer->writeLines($data);
return $resource;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace Xentral\Components\Exporter\Csv;
use Iterator;
use Xentral\Components\Exporter\Exception\InvalidResourceException;
use Xentral\Components\Exporter\Exception\PhpExtensionMissingException;
final class CsvWriter
{
/** @var resource $handle */
private $handle;
/** @var CsvConfig $config */
private $config;
/**
* @param resource $handle
* @param CsvConfig|null $config
*
* @throws InvalidResourceException If resource is not writable or invalid
* @throws PhpExtensionMissingException If mbstring is missing
*/
public function __construct($handle, CsvConfig $config = null)
{
if (!is_resource($handle)) {
throw new InvalidResourceException('First parameter is not a valid resource.');
}
if (!$this->isStreamWritable($handle)) {
throw new InvalidResourceException('Resource is not writable.');
}
if (!function_exists('mb_convert_encoding')) {
throw new PhpExtensionMissingException('Required PHP extension "mbstring" is missing.');
}
if ($config === null) {
$config = new CsvConfig();
}
$this->config = $config;
$this->handle = $handle;
}
/**
* @param array|Iterator $lines
*
* @return void
*/
public function writeLines($lines)
{
foreach ($lines as $line) {
$this->writeLine($line);
}
}
/**
* @param array $line
*
* @return void
*/
public function writeLine($line)
{
if ($this->config->isForceEnclosureEnabled()) {
$fields = $this->encloseAllValues($line);
fwrite(
$this->handle,
sprintf(
"%s\n",
implode($this->config->getDelimiter(), $this->convertCharset($fields))
)
);
return;
}
fputcsv(
$this->handle,
$this->convertCharset($line),
$this->config->getDelimiter(),
$this->config->getEnclosure(),
$this->config->getEscapeChar()
);
}
/***
* @param array $line
*
* @return array
*/
private function encloseAllValues($line)
{
$escapeChar = $this->config->getEscapeChar();
$enclosure = $this->config->getEnclosure();
$result = [];
foreach ($line as $key => $value) {
$value = str_replace($escapeChar . $enclosure, $enclosure, $value);
$value = str_replace($enclosure, $escapeChar . $enclosure, $value);
$result[$key] = sprintf('%1$s%2$s%1$s', $enclosure, $value);
}
return $result;
}
/**
* @param array $line
*
* @return array
*/
private function convertCharset($line)
{
if ($this->config->getSourceCharset() === $this->config->getTargetCharset()) {
return $line; // No conversion needed
}
$result = [];
foreach ($line as $key => $cellData) {
$result[$key] = mb_convert_encoding(
$cellData,
$this->config->getTargetCharset(),
$this->config->getSourceCharset()
);
}
return $result;
}
/**
* @param resource $handle
*
* @return bool
*/
private function isStreamWritable($handle)
{
$meta = stream_get_meta_data($handle);
$currentMode = $meta['mode'];
$writeModes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
foreach ($writeModes as $writeMode) {
if (strpos($currentMode, $writeMode) !== false) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface ExporterExceptionInterface extends ComponentExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use RuntimeException;
class FileExistsException extends RuntimeException implements ExporterExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use InvalidArgumentException as SplInvalidArgumentException;
class InvalidArgumentException extends SplInvalidArgumentException implements ExporterExceptionInterface
{
}

View File

@ -0,0 +1,62 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use Exception;
class InvalidJsonException extends Exception implements ExporterExceptionInterface
{
/**
* @param int $errorCode
*
* @return self
*/
public static function fromJsonError($errorCode)
{
$exception = new self(self::mapJsonError($errorCode));
return $exception;
}
private static function mapJsonError($jsonError)
{
switch ($jsonError) {
case JSON_ERROR_NONE:
$msg = 'Unknown error';
break;
case JSON_ERROR_DEPTH:
$msg = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
case JSON_ERROR_RECURSION:
$msg = 'One or more recursive references in the value to be encoded';
break;
case JSON_ERROR_INF_OR_NAN:
$msg = 'One or more NAN or INF values in the value to be encoded';
break;
case JSON_ERROR_UNSUPPORTED_TYPE:
$msg = 'A value of a type that cannot be encoded was given';
break;
case JSON_ERROR_INVALID_PROPERTY_NAME:
$msg = 'A property name that cannot be encoded was given';
break;
case JSON_ERROR_UTF16:
$msg = 'Malformed UTF-16 characters, possibly incorrectly encoded';
break;
default:
$msg = 'Unknown Error';
}
return $msg;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use RuntimeException;
class InvalidResourceException extends RuntimeException implements ExporterExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use LogicException;
class InvalidReturnTypeException extends LogicException implements ExporterExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use RuntimeException;
class PhpExtensionMissingException extends RuntimeException implements ExporterExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Exporter\Exception;
use Exception;
class ResourceWriteException extends Exception implements ExporterExceptionInterface
{
}

View File

@ -0,0 +1,27 @@
<?php
namespace Xentral\Components\Exporter\Json;
final class JsonConfig
{
/** @var int $options */
private $options;
/**
* @see https://www.php.net/manual/en/json.constants.php
*
* @param int $options
*/
public function __construct($options = 0)
{
$this->options = $options;
}
/**
* @return int
*/
public function getOptions()
{
return $this->options;
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace Xentral\Components\Exporter\Json;
use Xentral\Components\Exporter\Exception\FileExistsException;
use Xentral\Components\Exporter\Exception\InvalidResourceException;
final class JsonExporter
{
/** @var JsonConfig $config */
private $config;
/**
* @param JsonConfig|null $config
*/
public function __construct(JsonConfig $config = null)
{
if ($config === null) {
$config = new JsonConfig();
}
$this->config = $config;
}
/**
* @param string $filePath Resource used for writing
* @param array $data Multi-dimentional array
*
* @throws \Xentral\Components\Exporter\Exception\InvalidJsonException
* @throws \Xentral\Components\Exporter\Exception\ResourceWriteException
* @return void
*/
public function export($filePath, $data)
{
$resource = $this->exportToResource($filePath, $data);
fclose($resource);
}
/**
* Same as ::export() beside that the created resource will be returned
*
* @param string $filePath Resource used for writing
* @param array $data Multi-dimentional array
*
* @throws \Xentral\Components\Exporter\Exception\InvalidJsonException
* @throws \Xentral\Components\Exporter\Exception\ResourceWriteException
* @return resource
*/
public function exportToResource($filePath, $data)
{
if (is_file($filePath)) {
throw new FileExistsException(sprintf('File creation failed. File "%s" already exists.', $filePath));
}
// 'x+' = Create and open for reading and writing.
// File pointer will be placed at the beginning of the file.
// If the file already exists `fopen` will return false.
// 'b' = Enable binary mode
$resource = @fopen($filePath, 'x+b');
if ($resource === false) {
throw new InvalidResourceException(sprintf('Failed to open resource for file path "%s".', $filePath));
}
$writer = new JsonWriter($resource, $this->config);
$writer->write($data);
return $resource;
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Xentral\Components\Exporter\Json;
use JsonSerializable;
use Xentral\Components\Exporter\Exception\InvalidJsonException;
use Xentral\Components\Exporter\Exception\InvalidResourceException;
use Xentral\Components\Exporter\Exception\PhpExtensionMissingException;
use Xentral\Components\Exporter\Exception\ResourceWriteException;
final class JsonWriter
{
/** @var resource $handle */
private $handle;
/** @var JsonConfig $config */
private $config;
/**
* @param resource $handle
* @param JsonConfig|null $config
*
* @throws InvalidResourceException If resource is not writable or invalid
* @throws PhpExtensionMissingException If mbstring is missing
*/
public function __construct($handle, JsonConfig $config = null)
{
if (!is_resource($handle)) {
throw new InvalidResourceException('First parameter is not a valid resource.');
}
if (!$this->isStreamWritable($handle)) {
throw new InvalidResourceException('Resource is not writable.');
}
if (!function_exists('mb_convert_encoding')) {
throw new PhpExtensionMissingException('Required PHP extension "mbstring" is missing.');
}
if ($config === null) {
$config = new JsonConfig();
}
$this->config = $config;
$this->handle = $handle;
}
/**
* @param array|JsonSerializable $data
*
* @throws PhpExtensionMissingException If json is missing
* @throws ResourceWriteException
* @throws InvalidJsonException
*/
public function write($data)
{
if (!function_exists('json_encode')) {
throw new PhpExtensionMissingException('Required PHP extension "json" is missing.');
}
$jsonOptions = $this->config->getOptions();
$jsonString = @json_encode($data, $jsonOptions);
if ($jsonString === false) {
throw InvalidJsonException::fromJsonError(json_last_error());
}
$writeResult = @fwrite($this->handle, $jsonString);
if ($writeResult === false) {
throw new ResourceWriteException("JSON could not be written to resource.");
}
}
/**
* @param resource $handle
*
* @return bool
*/
private function isStreamWritable($handle)
{
$meta = stream_get_meta_data($handle);
$currentMode = $meta['mode'];
$writeModes = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
foreach ($writeModes as $writeMode) {
if (strpos($currentMode, $writeMode) !== false) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,127 @@
# CSV-Exporter
## Datei erstellen
```php
<?php
use Xentral\Components\Database\Database;
use Xentral\Components\Exporter\Csv\CsvExporter;
/** @var Database $db */
$db = $container->get('Database');
$data = $db->yieldAll('SELECT e.* FROM employees AS e');
$filePath = tempnam(sys_get_temp_dir(), 'employees') . '.csv';
$exporter = new CsvExporter();
$exporter->export($filePath, $data);
```
##### Rückgabewerte
* Die `export()` Methode liefert nichts zurück.
* Die `exportToResource()` gibt die geöffnete Ressource zurück. Die Ressource muss manuell (mit `fclose()`)
geschlossen werden. Der Dateizeiger ist auf `EOF` platziert.
##### Funktionsparameter
Beide `export*()`-Methoden nehmen die gleichen Parameter entgegen.
* Als erster Parameter wird ein absoluter Dateipfad erwartet. Alternativ kann ein Stream Wrapper angegeben werden.
Beispiele:
* `php://output` um die CSV direkt auszugeben (`echo`).
* `php://memory` um in den Arbeitsspeicher zu schreiben.
* `php://temp` um in eine temporäre Datei zu schreiben.
* Der zweite Parameter nimmt die Daten entgegen die als CSV exportiert werden sollen. Folgende Typen sind erlaubt:
* `array`
* `Generator`
* `Iterator`
* `IteratorAggregate`
##### Konstruktor-Parameter
* Der erste Konstruktor-Parameter ist optional und nimmt die CSV-Konfiguration entgegen;
siehe _Erweiterte Beispiele_ > _CSV konfigurieren_.
## Datei-Download erstellen
```php
<?php
use Xentral\Components\Database\Database;
use Xentral\Components\Exporter\Csv\CsvExporter;
/** @var Database $db */
$db = $container->get('Database');
$data = $db->yieldAll('SELECT e.* FROM employees AS e');
$exporter = new CsvExporter();
$resource = $exporter->exportToResource('php://memory', $data);
rewind($resource);
$stat = fstat($resource);
header('Cache-Control: must-revalidate');
header('Pragma: must-revalidate');
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="employees.csv"');
header('Content-Length: ' . $stat['size']);
fpassthru($resource);
fclose($resource);
```
## Erweiterte Beispiele
### CSV konfigurieren
```php
<?php
use Xentral\Components\Exporter\Csv\CsvConfig;
use Xentral\Components\Exporter\Csv\CsvExporter;
$csvConfig = new CsvConfig();
$csvConfig->setDelimiter(';');
$csvConfig->setEnclosure('"');
$csvConfig->setSourceCharset('UTF-8');
$csvConfig->setTargetCharset('ISO-8859-1');
$exporter = new CsvExporter($csvConfig);
$exporter->export($filePath, $data);
```
### Daten zusammenführen
```php
<?php
use Xentral\Components\Exporter\Csv\CsvExporter;
use Xentral\Components\Exporter\Collection\DataCollection;
$collection = new DataCollection($headline, $employees);
$exporter = new CsvExporter();
$exporter->export($filePath, $collection);
```
### Daten formatieren
```php
<?php
use Xentral\Components\Exporter\Csv\CsvExporter;
use Xentral\Components\Exporter\Collection\FormatterCollection;
$formatter = new FormatterCollection($data, function ($row) {
$row['fullname'] = $row['firstname'] . ' ' . $row['lastname'];
return $row;
});
$exporter = new CsvExporter();
$exporter->export($filePath, $formatter);
```
Statt einer anonymen Funktion (`Closure`) kann ein `callable` übergeben werden.

View File

@ -0,0 +1,96 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
interface AdapterInterface extends ReaderAdapterInterface
{
/**
* Creates a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function write($path, $contents, array $config = []);
/**
* Creates a new file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function writeStream($path, $resource, array $config = []);
/**
* Updates an existing file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function update($path, $contents, array $config = []);
/**
* Updates an existing file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function updateStream($path, $resource, array $config = []);
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function rename($path, $newpath);
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function copy($path, $newpath);
/**
* Deletes a single file
*
* @param string $path
*
* @return bool
*/
public function delete($path);
/**
* Deletes a directory and its contents
*
* @param string $directory
*
* @return bool
*/
public function deleteDir($directory);
/**
* @param string $directory
* @param array $config
*
* @return bool
*/
public function createDir($directory, array $config = []);
}

View File

@ -0,0 +1,157 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
final class FtpConfig
{
/** @var string $hostname */
private $hostname;
/** @var string $username */
private $username;
/** @var string $password */
private $password;
/** @var string $rootDir */
private $rootDir;
/** @var int $port */
private $port;
/** @var int $timeout */
private $timeout;
/** @var bool $passive */
private $passive;
/** @var bool $ssl */
private $ssl;
/**
* @param string $hostname
* @param string $username
* @param string $password
* @param string $rootDir
* @param int $port
* @param int $timeout
* @param bool $passive
* @param bool $ssl
*/
public function __construct(
$hostname,
$username,
$password,
$rootDir = '/',
$port = 21,
$timeout = 30,
$passive = true,
$ssl = false
) {
if (empty($hostname)) {
throw new InvalidArgumentException('Hostname is empty.');
}
if (empty($username)) {
throw new InvalidArgumentException('Username is empty.');
}
if (empty($password)) {
throw new InvalidArgumentException('Password is empty.');
}
if (empty($rootDir)) {
throw new InvalidArgumentException('Root dir is empty.');
}
$this->hostname = (string)$hostname;
$this->username = (string)$username;
$this->password = (string)$password;
$this->rootDir = (string)$rootDir;
$this->port = (int)$port;
$this->timeout = (int)$timeout;
$this->passive = (bool)$passive;
$this->ssl = (bool)$ssl;
}
/**
* @return array
*/
public function toArray()
{
return [
'host' => $this->hostname,
'username' => $this->username,
'password' => $this->password,
'root' => $this->rootDir,
'port' => $this->port,
'timeout' => $this->timeout,
'passive' => $this->passive,
'ssl' => $this->ssl,
'recurseManually' => true,
];
}
/**
* @return string
*/
public function getHostname()
{
return $this->hostname;
}
/**
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* @return null
*/
public function getRootDir()
{
return $this->rootDir;
}
/**
* @return int
*/
public function getPort()
{
return $this->port;
}
/**
* @return int
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* @return bool
*/
public function isPassive()
{
return $this->passive;
}
/**
* @return bool
*/
public function isSsl()
{
return $this->ssl;
}
}

View File

@ -0,0 +1,355 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
use League\Flysystem\AdapterInterface as LeagueAdapterInterface;
use League\Flysystem\Config as LeagueConfig;
use League\Flysystem\Util;
use League\Flysystem\Util\ContentListingFormatter;
use Xentral\Components\Filesystem\PathInfo;
final class LeagueAdapterWrapper implements AdapterInterface
{
/** @var LeagueAdapterInterface $league */
private $league;
/** @var bool $caseSensitive */
private $caseSensitive = true;
/**
* @param LeagueAdapterInterface $league
*/
public function __construct(LeagueAdapterInterface $league)
{
$this->league = $league;
}
/**
* @param string $path
*
* @return bool
*/
public function has($path)
{
$path = $this->normalizePath($path);
return (bool)$this->league->has($path) !== false;
}
/**
* @param string $path
*
* @return PathInfo|false
*/
public function getInfo($path)
{
$path = $this->normalizePath($path);
$metainfo = $this->league->getMetadata($path);
if (!$metainfo) {
return false;
}
$directory = Util::dirname($path);
$metainfo['path'] = $path;
$formatter = new ContentListingFormatter($directory, false, $this->caseSensitive);
$contents = $formatter->formatListing([$metainfo]);
if (count($contents) !== 1) {
return false;
}
return new PathInfo($contents[0]);
}
/**
* @param string $path
*
* @return string|false
*/
public function read($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMetadata($path);
if ($meta['type'] === self::TYPE_DIR) {
return false;
}
$result = $this->league->read($path);
if (!$result || !isset($result['contents'])) {
return false;
}
return $result['contents'];
}
/**
* @param string $path
*
* @return resource|false
*/
public function readStream($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMetadata($path);
if ($meta['type'] === self::TYPE_DIR) {
return false;
}
$result = $this->league->readStream($path);
if (!$result || !isset($result['stream'])) {
return false;
}
return $result['stream'];
}
/**
* @param string $directory
* @param bool $recursive
*
* @return array
*/
public function listContents($directory = '', $recursive = false)
{
$directory = $this->normalizePath($directory);
$contents = $this->getLeagueAdapter()->listContents($directory, $recursive);
$formatter = new ContentListingFormatter($directory, $recursive, $this->caseSensitive);
return $formatter->formatListing($contents);
}
/**
* @param string $path
*
* @return array|false
*/
public function getMetadata($path)
{
$path = $this->normalizePath($path);
return $this->league->getMetadata($path);
}
/**
* @param string $path
*
* @return string|false
*/
public function getType($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMetadata($path);
if ($meta === false || !isset($meta['type'])) {
return false;
}
return $meta['type'];
}
/**
* @param string $path
*
* @return int|false
*/
public function getSize($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getSize($path);
if ($meta === false || !isset($meta['size'])) {
return false;
}
return (int)$meta['size'];
}
/**
* @param string $path
*
* @return int|false
*/
public function getTimestamp($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getTimestamp($path);
if ($meta === false || !isset($meta['timestamp'])) {
return false;
}
return (int)$meta['timestamp'];
}
/**
* @param string $path
*
* @return string|false
*/
public function getMimetype($path)
{
$path = $this->normalizePath($path);
$meta = $this->league->getMimetype($path);
if ($meta === false || !isset($meta['mimetype'])) {
return false;
}
if ($meta['type'] === 'dir') {
return 'directory';
}
return $meta['mimetype'];
}
/**
* Creates a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function write($path, $contents, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->write($path, $contents, new LeagueConfig($config)) !== false;
}
/**
* Creates a new file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function writeStream($path, $resource, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->writeStream($path, $resource, new LeagueConfig($config)) !== false;
}
/**
* Updates an existing file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function update($path, $contents, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->update($path, $contents, new LeagueConfig($config)) !== false;
}
/**
* Updates an existing file using a stream
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @return bool
*/
public function updateStream($path, $resource, array $config = [])
{
$path = $this->normalizePath($path);
return $this->league->updateStream($path, $resource, new LeagueConfig($config)) !== false;
}
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function rename($path, $newpath)
{
$path = $this->normalizePath($path);
$newpath = $this->normalizePath($newpath);
return $this->league->rename($path, $newpath);
}
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @return bool
*/
public function copy($path, $newpath)
{
$path = $this->normalizePath($path);
$newpath = $this->normalizePath($newpath);
return $this->league->copy($path, $newpath);
}
/**
* Deletes a single file
*
* @param string $path
*
* @return bool
*/
public function delete($path)
{
$path = $this->normalizePath($path);
return $this->league->delete($path);
}
/**
* Deletes a directory and its contents
*
* @param string $directory
*
* @return bool
*/
public function deleteDir($directory)
{
$directory = $this->normalizePath($directory);
return $this->league->deleteDir($directory);
}
/**
* @param string $directory
* @param array $config
*
* @return bool
*/
public function createDir($directory, array $config = [])
{
$directory = $this->normalizePath($directory);
return $this->league->createDir($directory, new LeagueConfig($config)) !== false;
}
/**
* @return LeagueAdapterInterface
*/
public function getLeagueAdapter()
{
return $this->league;
}
/**
* @param string $path
*
* @return string
*/
public function normalizePath($path)
{
return Util::normalizePath($path);
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Xentral\Components\Filesystem\Adapter;
use Xentral\Components\Filesystem\PathInfo;
interface ReaderAdapterInterface
{
const TYPE_DIR = 'dir';
const TYPE_FILE = 'file';
/**
* @param string $path
*
* @return bool
*/
public function has($path);
/**
* @param string $path
*
* @return PathInfo|false
*/
public function getInfo($path);
/**
* @param string $path
*
* @return string|false
*/
public function read($path);
/**
* @param string $path
*
* @return resource|false
*/
public function readStream($path);
/**
* @param string $directory
* @param bool $recursive
*
* @return array
*/
public function listContents($directory = '', $recursive = false);
/**
* @param string $path
*
* @return string|false [dir|file]
*/
public function getType($path);
/**
* @param string $path
*
* @return array|false
*/
public function getMetadata($path);
/**
* @param string $path
*
* @return int|false
*/
public function getSize($path);
/**
* @param string $path
*
* @return int|false
*/
public function getTimestamp($path);
/**
* @param string $path
*
* @return string|false
*/
public function getMimetype($path);
}

View File

@ -0,0 +1,28 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Core\DependencyInjection\ContainerInterface;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices()
{
return [
'FilesystemFactory' => 'onInitFilesystemFactory',
];
}
/**
* @param ContainerInterface $container
*
* @return FilesystemFactory
*/
public static function onInitFilesystemFactory(ContainerInterface $container)
{
return new FilesystemFactory($container->get('Database'));
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class DirNotFoundException extends RuntimeException implements FilesystemExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class FileExistsException extends RuntimeException implements FilesystemExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class FileNotFoundException extends RuntimeException implements FilesystemExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use RuntimeException;
class FilesystemException extends RuntimeException implements FilesystemExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use Xentral\Core\Exception\ComponentExceptionInterface;
interface FilesystemExceptionInterface extends ComponentExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use InvalidArgumentException as SplInvalidArgumentException;
class InvalidArgumentException extends SplInvalidArgumentException implements FilesystemExceptionInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Xentral\Components\Filesystem\Exception;
use LogicException;
class RootViolationException extends LogicException implements FilesystemExceptionInterface
{
}

View File

@ -0,0 +1,429 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Components\Filesystem\Adapter\AdapterInterface;
use Xentral\Components\Filesystem\Exception\DirNotFoundException;
use Xentral\Components\Filesystem\Exception\FileExistsException;
use Xentral\Components\Filesystem\Exception\FileNotFoundException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
use Xentral\Components\Filesystem\Exception\RootViolationException;
final class Filesystem implements FilesystemInterface
{
/** @var AdapterInterface $adapter */
private $adapter;
/**
* @param AdapterInterface $adapter
*/
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* Checks if file or directory exists
*
* @param string $path
*
* @return bool
*/
public function has($path)
{
return $this->adapter->has($path);
}
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return PathInfo|false
*/
public function getInfo($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getInfo($path);
}
/**
* List directory contents
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listContents($directory = '', $recursive = false)
{
$result = [];
$contents = $this->adapter->listContents($directory, $recursive);
foreach ($contents as $metainfo) {
$result[] = PathInfo::fromMeta($metainfo);
}
return $result;
}
/**
* Lists only directories
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listDirs($directory = '', $recursive = false)
{
$result = [];
$contents = $this->adapter->listContents($directory, $recursive);
foreach ($contents as $metainfo) {
if ($metainfo['type'] === 'dir') {
$result[] = PathInfo::fromMeta($metainfo);
}
}
return $result;
}
/**
* Lists only files
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listFiles($directory = '', $recursive = false)
{
$result = [];
$contents = $this->adapter->listContents($directory, $recursive);
foreach ($contents as $metainfo) {
if ($metainfo['type'] === 'file') {
$result[] = PathInfo::fromMeta($metainfo);
}
}
return $result;
}
/**
* List only paths as strings
*
* @param string $directory
* @param bool $recursive
*
* @return array|string[]
*/
public function listPaths($directory = '', $recursive = false)
{
$contents = $this->adapter->listContents($directory, $recursive);
return array_column($contents, 'path');
}
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string [dir|file]
*/
public function getType($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getType($path);
}
/**
* Gets the filesize
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return int|false
*/
public function getSize($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getSize($path);
}
/**
* Gets the timestamp from last update
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getTimestamp($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getTimestamp($path);
}
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getMimetype($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->getMimetype($path);
}
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function read($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->read($path);
}
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return resource|false
*/
public function readStream($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->readStream($path);
}
/**
* Writes to a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @throws FileExistsException
*
* @return bool
*/
public function write($path, $contents, array $config = [])
{
if ($this->has($path)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $path));
}
return $this->adapter->write($path, $contents, $config);
}
/**
* Writes to a new file
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
* @throws FileExistsException
*
* @return bool
*/
public function writeStream($path, $resource, array $config = [])
{
if ($this->has($path)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $path));
}
if (!is_resource($resource)) {
throw new InvalidArgumentException('Second parameter must be a resource.');
}
return $this->adapter->writeStream($path, $resource, $config);
}
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function put($path, $contents, array $config = [])
{
if (!$this->has($path)) {
return $this->adapter->write($path, $contents, $config);
}
return $this->adapter->update($path, $contents, $config);
}
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function putStream($path, $resource, array $config = [])
{
if (!is_resource($resource)) {
throw new InvalidArgumentException('Second parameter must be a resource.');
}
if (!$this->has($path)) {
return $this->adapter->writeStream($path, $resource, $config);
}
return $this->adapter->updateStream($path, $resource, $config);
}
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function rename($path, $newpath)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
if ($this->has($newpath)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $newpath));
}
return $this->adapter->rename($path, $newpath);
}
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function copy($path, $newpath)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
if ($this->has($newpath)) {
throw new FileExistsException(sprintf('File "%s" exists already.', $newpath));
}
return $this->adapter->copy($path, $newpath);
}
/**
* Deletes a single file
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return bool
*/
public function delete($path)
{
if (!$this->has($path)) {
throw new FileNotFoundException(sprintf('File "%s" not found.', $path));
}
return $this->adapter->delete($path);
}
/**
* Deletes a directory and all its contents
*
* @param string $dirname
*
* @throws DirNotFoundException
* @throws RootViolationException
*
* @return bool
*/
public function deleteDir($dirname)
{
$info = $this->adapter->getInfo($dirname);
if (!$info) {
throw new DirNotFoundException(sprintf('Directory "%s" not found.', $dirname));
}
if ($info->getPath() === '') {
throw new RootViolationException('Root directory can not be deleted.');
}
return $this->adapter->deleteDir($dirname);
}
/**
* Creates a directory
*
* @param string $dirname
* @param array $config
*
* @return bool
*/
public function createDir($dirname, array $config = [])
{
return $this->adapter->createDir($dirname, $config);
}
/**
* @return AdapterInterface
*/
public function getAdapter()
{
return $this->adapter;
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Xentral\Components\Filesystem;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Components\Filesystem\Adapter\FtpConfig;
use Xentral\Components\Filesystem\Adapter\LeagueAdapterWrapper;
use Xentral\Components\Filesystem\Exception\FilesystemException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
use Xentral\Components\Filesystem\Flysystem\FtpAdapterDecorator;
use Xentral\Components\Filesystem\Flysystem\LocalAdapterDecorator;
final class FilesystemFactory
{
/** @var Database $db */
private $db;
/**
* @param Database $database
*/
public function __construct(Database $database)
{
$this->db = $database;
}
/**
* @param string $root Absolute path
* @param array $config
*
* @return FilesystemInterface
*/
public function createLocal($root, array $config = [])
{
try {
$writeFlags = isset($config['write_flags']) ? $config['write_flags'] : LOCK_EX;
$linkHandling = isset($config['link_handling']) ? $config['link_handling'] : LocalAdapterDecorator::SKIP_LINKS;
$permissions = isset($config['permissions']) ? $config['permissions'] : [];
$leagueLocalAdapter = new LocalAdapterDecorator($root, $writeFlags, $linkHandling, $permissions);
$leagueAdapterWrapper = new LeagueAdapterWrapper($leagueLocalAdapter);
return new Filesystem($leagueAdapterWrapper);
//
} catch (Exception $e) {
throw new FilesystemException($e->getMessage(), (int)$e->getCode(), $e);
}
}
/**
* @param FtpConfig $config
*
* @return FilesystemInterface
*/
public function createFtp(FtpConfig $config)
{
try {
$leagueFtpAdapter = new FtpAdapterDecorator($config->toArray());
$leagueAdapterWrapper = new LeagueAdapterWrapper($leagueFtpAdapter);
return new Filesystem($leagueAdapterWrapper);
//
} catch (Exception $e) {
throw new FilesystemException($e->getMessage(), (int)$e->getCode(), $e);
}
}
/**
* @param FilesystemInterface $filesystem
* @param int $syncId
*
* @return FilesystemSyncCache
*/
public function createSync(FilesystemInterface $filesystem, $syncId)
{
try {
if (get_class($filesystem) === FilesystemSyncCache::class) {
throw new InvalidArgumentException('FilesystemSyncWrapper can not wrap it self.');
}
return new FilesystemSyncCache($this->db, $filesystem, $syncId);
//
} catch (Exception $e) {
throw new FilesystemException($e->getMessage(), (int)$e->getCode(), $e);
}
}
}

View File

@ -0,0 +1,256 @@
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Components\Filesystem\Adapter\AdapterInterface;
use Xentral\Components\Filesystem\Exception\DirNotFoundException;
use Xentral\Components\Filesystem\Exception\FileExistsException;
use Xentral\Components\Filesystem\Exception\FileNotFoundException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
use Xentral\Components\Filesystem\Exception\RootViolationException;
interface FilesystemInterface
{
/**
* Checks if file or directory exists
*
* @param string $path
*
* @return bool
*/
public function has($path);
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return PathInfo|false
*/
public function getInfo($path);
/**
* List directory contents
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listContents($directory = '', $recursive = false);
// public function filterContents(array $filter = [], $directory = '', $recursive = false); // @todo
//
// // @todo ExtendedLocal
// public function isReadable();
// public function isWriteable();
// public function getOwner();
// public function getGroup();
/**
* Lists only directories
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listDirs($directory = '', $recursive = false);
/**
* Lists only files
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listFiles($directory = '', $recursive = false);
/**
* List only paths as strings
*
* @param string $directory
* @param bool $recursive
*
* @return array|string[]
*/
public function listPaths($directory = '', $recursive = false);
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string [dir|file]
*/
public function getType($path);
/**
* Gets the filesize
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return int|false
*/
public function getSize($path);
/**
* Gets the timestamp from last update
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getTimestamp($path);
/**
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function getMimetype($path);
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return string|false
*/
public function read($path);
/**
* Read file content
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return resource|false
*/
public function readStream($path);
/**
* Writes to a new file
*
* @param string $path
* @param string $contents
* @param array $config
*
* @throws FileExistsException
*
* @return bool
*/
public function write($path, $contents, array $config = []);
/**
* Writes to a new file
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
* @throws FileExistsException
*
* @return bool
*/
public function writeStream($path, $resource, array $config = []);
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param string $contents
* @param array $config
*
* @return bool
*/
public function put($path, $contents, array $config = []);
/**
* Creates a file or updates the file contents
*
* @param string $path
* @param resource $resource
* @param array $config
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function putStream($path, $resource, array $config = []);
/**
* Renames a file
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function rename($path, $newpath);
/**
* Copies a file to new location
*
* @param string $path
* @param string $newpath
*
* @throws FileExistsException
* @throws FileNotFoundException
*
* @return bool
*/
public function copy($path, $newpath);
/**
* Deletes a single file
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return bool
*/
public function delete($path);
/**
* Deletes a directory and all its contents
*
* @param string $dirname
*
* @throws DirNotFoundException
* @throws RootViolationException
*
* @return bool
*/
public function deleteDir($dirname);
/**
* Creates a directory
*
* @param string $dirname
* @param array $config
*
* @return bool
*/
public function createDir($dirname, array $config = []);
/**
* @return AdapterInterface
*/
public function getAdapter();
}

Some files were not shown because too many files have changed in this diff Show More