summaryrefslogtreecommitdiffstats
path: root/vendor/gipfl
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gipfl')
-rw-r--r--vendor/gipfl/calendar/composer.json32
-rw-r--r--vendor/gipfl/calendar/src/Calendar.php246
-rw-r--r--vendor/gipfl/calendar/src/Widget/CalendarMonth.php177
-rw-r--r--vendor/gipfl/calendar/src/Widget/CalendarMonthSummary.php278
-rw-r--r--vendor/gipfl/cli/composer.json31
-rw-r--r--vendor/gipfl/cli/src/AnsiScreen.php128
-rw-r--r--vendor/gipfl/cli/src/Process.php141
-rw-r--r--vendor/gipfl/cli/src/Screen.php190
-rw-r--r--vendor/gipfl/cli/src/Spinner.php69
-rw-r--r--vendor/gipfl/cli/src/Tty.php132
-rw-r--r--vendor/gipfl/cli/src/TtyMode.php95
-rw-r--r--vendor/gipfl/curl/LICENSE21
-rw-r--r--vendor/gipfl/curl/composer.json29
-rw-r--r--vendor/gipfl/curl/src/CurlAsync.php338
-rw-r--r--vendor/gipfl/curl/src/CurlHandle.php76
-rw-r--r--vendor/gipfl/curl/src/RequestError.php44
-rw-r--r--vendor/gipfl/curl/src/ResponseParseError.php7
-rw-r--r--vendor/gipfl/data-type/composer.json24
-rw-r--r--vendor/gipfl/data-type/src/SetOfSettings.php79
-rw-r--r--vendor/gipfl/data-type/src/Settings.php103
-rw-r--r--vendor/gipfl/db-migration/composer.json16
-rw-r--r--vendor/gipfl/db-migration/src/Migration.php73
-rw-r--r--vendor/gipfl/db-migration/src/Migrations.php299
-rw-r--r--vendor/gipfl/diff/LICENSE21
-rw-r--r--vendor/gipfl/diff/composer.json25
-rw-r--r--vendor/gipfl/diff/public/css/diff.less133
-rw-r--r--vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php10
-rw-r--r--vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php10
-rw-r--r--vendor/gipfl/diff/src/PhpDiff.php147
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php54
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php144
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Ratio.php139
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php44
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php207
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php104
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php121
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php98
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php52
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php438
-rw-r--r--vendor/gipfl/format/composer.json28
-rw-r--r--vendor/gipfl/format/src/LocalDateFormat.php41
-rw-r--r--vendor/gipfl/format/src/LocalTimeFormat.php184
-rw-r--r--vendor/gipfl/format/src/LocaleAwareness.php94
-rw-r--r--vendor/gipfl/icinga-cli-daemon/composer.json24
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php163
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php66
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php144
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php45
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php88
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php244
-rw-r--r--vendor/gipfl/icinga-cli-daemon/src/StateMachine.php113
-rw-r--r--vendor/gipfl/icingaweb2/composer.json21
-rw-r--r--vendor/gipfl/icingaweb2/public/css/11-action-bar.less105
-rw-r--r--vendor/gipfl/icingaweb2/public/css/12-quicksearch.less17
-rw-r--r--vendor/gipfl/icingaweb2/src/CompatController.php581
-rw-r--r--vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php30
-rw-r--r--vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php46
-rw-r--r--vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php173
-rw-r--r--vendor/gipfl/icingaweb2/src/Data/Paginatable.php64
-rw-r--r--vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php68
-rw-r--r--vendor/gipfl/icingaweb2/src/FakeRequest.php32
-rw-r--r--vendor/gipfl/icingaweb2/src/Icon.php27
-rw-r--r--vendor/gipfl/icingaweb2/src/IconHelper.php89
-rw-r--r--vendor/gipfl/icingaweb2/src/Img.php83
-rw-r--r--vendor/gipfl/icingaweb2/src/Link.php85
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php29
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php74
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php263
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php281
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php34
-rw-r--r--vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php149
-rw-r--r--vendor/gipfl/icingaweb2/src/Translator.php26
-rw-r--r--vendor/gipfl/icingaweb2/src/Url.php162
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/ActionBar.php25
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Content.php14
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Controls.php163
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php60
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/ListItem.php26
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php29
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Paginator.php463
-rw-r--r--vendor/gipfl/icingaweb2/src/Widget/Tabs.php44
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php93
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php335
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php111
-rw-r--r--vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php115
-rw-r--r--vendor/gipfl/influxdb/LICENSE21
-rw-r--r--vendor/gipfl/influxdb/composer.json25
-rw-r--r--vendor/gipfl/influxdb/src/ChunkedInfluxDbWriter.php158
-rw-r--r--vendor/gipfl/influxdb/src/DataPoint.php63
-rw-r--r--vendor/gipfl/influxdb/src/Escape.php67
-rw-r--r--vendor/gipfl/influxdb/src/InfluxDbConnection.php24
-rw-r--r--vendor/gipfl/influxdb/src/InfluxDbConnectionFactory.php38
-rw-r--r--vendor/gipfl/influxdb/src/InfluxDbConnectionV1.php311
-rw-r--r--vendor/gipfl/influxdb/src/InfluxDbConnectionV2.php270
-rw-r--r--vendor/gipfl/influxdb/src/InfluxDbQueryResult.php65
-rw-r--r--vendor/gipfl/influxdb/src/LineProtocol.php63
-rw-r--r--vendor/gipfl/json/composer.json20
-rw-r--r--vendor/gipfl/json/src/JsonDecodeException.php7
-rw-r--r--vendor/gipfl/json/src/JsonEncodeException.php7
-rw-r--r--vendor/gipfl/json/src/JsonException.php55
-rw-r--r--vendor/gipfl/json/src/JsonSerialization.php14
-rw-r--r--vendor/gipfl/json/src/JsonString.php68
-rw-r--r--vendor/gipfl/json/src/SerializationHelper.php55
-rw-r--r--vendor/gipfl/linux-health/composer.json23
-rw-r--r--vendor/gipfl/linux-health/src/Cpu.php59
-rw-r--r--vendor/gipfl/linux-health/src/Memory.php52
-rw-r--r--vendor/gipfl/linux-health/src/Network.php36
-rw-r--r--vendor/gipfl/log/LICENSE21
-rw-r--r--vendor/gipfl/log/composer.json31
-rw-r--r--vendor/gipfl/log/src/AdditionalContextLogger.php25
-rw-r--r--vendor/gipfl/log/src/DummyLogger.php44
-rw-r--r--vendor/gipfl/log/src/Filter/LogLevelFilter.php39
-rw-r--r--vendor/gipfl/log/src/Formatter/StdOutFormatter.php34
-rw-r--r--vendor/gipfl/log/src/IcingaWeb/IcingaLogger.php29
-rw-r--r--vendor/gipfl/log/src/IcingaWeb/LoggerLogWriter.php32
-rw-r--r--vendor/gipfl/log/src/LogFilter.php14
-rw-r--r--vendor/gipfl/log/src/LogFormatter.php8
-rw-r--r--vendor/gipfl/log/src/LogLevel.php66
-rw-r--r--vendor/gipfl/log/src/LogWriter.php8
-rw-r--r--vendor/gipfl/log/src/LogWriterWithContext.php8
-rw-r--r--vendor/gipfl/log/src/Logger.php169
-rw-r--r--vendor/gipfl/log/src/PrefixLogger.php25
-rw-r--r--vendor/gipfl/log/src/Writer/JournaldLogger.php61
-rw-r--r--vendor/gipfl/log/src/Writer/JsonRpcConnectionWriter.php51
-rw-r--r--vendor/gipfl/log/src/Writer/JsonRpcWriter.php54
-rw-r--r--vendor/gipfl/log/src/Writer/ProxyLogWriter.php14
-rw-r--r--vendor/gipfl/log/src/Writer/SyslogWriter.php34
-rw-r--r--vendor/gipfl/log/src/Writer/SystemdStdoutWriter.php61
-rw-r--r--vendor/gipfl/log/src/Writer/WritableStreamWriter.php44
-rw-r--r--vendor/gipfl/openrpc/composer.json24
-rw-r--r--vendor/gipfl/openrpc/src/Components.php68
-rw-r--r--vendor/gipfl/openrpc/src/Contact.php49
-rw-r--r--vendor/gipfl/openrpc/src/ContentDescriptor.php73
-rw-r--r--vendor/gipfl/openrpc/src/Error.php54
-rw-r--r--vendor/gipfl/openrpc/src/Example.php36
-rw-r--r--vendor/gipfl/openrpc/src/ExamplePairing.php30
-rw-r--r--vendor/gipfl/openrpc/src/ExternalDocumentation.php37
-rw-r--r--vendor/gipfl/openrpc/src/Info.php69
-rw-r--r--vendor/gipfl/openrpc/src/License.php37
-rw-r--r--vendor/gipfl/openrpc/src/Link.php91
-rw-r--r--vendor/gipfl/openrpc/src/Method.php133
-rw-r--r--vendor/gipfl/openrpc/src/OpenRpcDocument.php75
-rw-r--r--vendor/gipfl/openrpc/src/Reference.php32
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/MetaDataClass.php76
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/MetaDataMethod.php111
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/MetaDataParameter.php46
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/MetaDataTagParser.php60
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/MetaDataTagSet.php69
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/MethodCommentParser.php163
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/ParamTag.php41
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/ReturnTag.php7
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/Tag.php37
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/ThrowsTag.php7
-rw-r--r--vendor/gipfl/openrpc/src/Reflection/TypeDescriptionTag.php27
-rw-r--r--vendor/gipfl/openrpc/src/Server.php74
-rw-r--r--vendor/gipfl/openrpc/src/ServerVariable.php46
-rw-r--r--vendor/gipfl/openrpc/src/SimpleJsonSerializer.php14
-rw-r--r--vendor/gipfl/openrpc/src/TagObject.php48
-rw-r--r--vendor/gipfl/process/composer.json25
-rw-r--r--vendor/gipfl/process/src/FinishedProcessState.php66
-rw-r--r--vendor/gipfl/process/src/ProcessInfo.php89
-rw-r--r--vendor/gipfl/process/src/ProcessKiller.php81
-rw-r--r--vendor/gipfl/process/src/ProcessList.php44
-rw-r--r--vendor/gipfl/protocol-jsonrpc/LICENSE21
-rw-r--r--vendor/gipfl/protocol-jsonrpc/composer.json34
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Connection.php310
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Error.php199
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Handler/FailingPacketHandler.php28
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Handler/JsonRpcHandler.php23
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Handler/NamespacedPacketHandler.php217
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/JsonRpcConnection.php241
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Notification.php98
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Packet.php226
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/PacketHandler.php11
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Request.php59
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/Response.php128
-rw-r--r--vendor/gipfl/protocol-jsonrpc/src/TestCase.php44
-rw-r--r--vendor/gipfl/protocol-netstring/LICENSE21
-rw-r--r--vendor/gipfl/protocol-netstring/composer.json25
-rw-r--r--vendor/gipfl/protocol-netstring/src/StreamWrapper.php113
-rw-r--r--vendor/gipfl/protocol/LICENSE21
-rw-r--r--vendor/gipfl/protocol/composer.json26
-rw-r--r--vendor/gipfl/protocol/src/Exception/ProtocolError.php9
-rw-r--r--vendor/gipfl/protocol/src/Generic/AbstractStreamWrapper.php139
-rw-r--r--vendor/gipfl/react-utils/LICENSE21
-rw-r--r--vendor/gipfl/react-utils/composer.json24
-rw-r--r--vendor/gipfl/react-utils/src/RetryUnless.php256
-rw-r--r--vendor/gipfl/simple-daemon/composer.json29
-rw-r--r--vendor/gipfl/simple-daemon/src/Daemon.php156
-rw-r--r--vendor/gipfl/simple-daemon/src/DaemonState.php172
-rw-r--r--vendor/gipfl/simple-daemon/src/DaemonTask.php20
-rw-r--r--vendor/gipfl/simple-daemon/src/SystemdAwareTask.php13
-rw-r--r--vendor/gipfl/socket/composer.json29
-rw-r--r--vendor/gipfl/socket/src/ConnectionList.php87
-rw-r--r--vendor/gipfl/socket/src/UnixSocketInspection.php89
-rw-r--r--vendor/gipfl/socket/src/UnixSocketPeer.php102
-rw-r--r--vendor/gipfl/stream/composer.json26
-rw-r--r--vendor/gipfl/stream/src/BufferedLineReader.php100
-rw-r--r--vendor/gipfl/systemd/LICENSE21
-rw-r--r--vendor/gipfl/systemd/composer.json26
-rw-r--r--vendor/gipfl/systemd/src/NotificationSocket.php122
-rw-r--r--vendor/gipfl/systemd/src/NotifySystemD.php292
-rw-r--r--vendor/gipfl/systemd/src/systemd.php19
-rw-r--r--vendor/gipfl/translation/LICENSE21
-rw-r--r--vendor/gipfl/translation/composer.json20
-rw-r--r--vendor/gipfl/translation/src/NoTranslator.php11
-rw-r--r--vendor/gipfl/translation/src/StaticTranslator.php31
-rw-r--r--vendor/gipfl/translation/src/TranslationHelper.php37
-rw-r--r--vendor/gipfl/translation/src/TranslatorInterface.php8
-rw-r--r--vendor/gipfl/translation/src/WrapTranslator.php26
-rw-r--r--vendor/gipfl/web/LICENSE21
-rw-r--r--vendor/gipfl/web/composer.json25
-rw-r--r--vendor/gipfl/web/public/css/01-gipfl-base.less8
-rw-r--r--vendor/gipfl/web/public/css/21-gipfl-collapsible.less55
-rw-r--r--vendor/gipfl/web/public/css/21-gipfl-widget-hint.less57
-rw-r--r--vendor/gipfl/web/public/css/31-gipfl-name-value-table.less27
-rw-r--r--vendor/gipfl/web/public/css/40-gipfl-form.less69
-rw-r--r--vendor/gipfl/web/public/css/41-director-form.less339
-rw-r--r--vendor/gipfl/web/public/css/42-director-extensible-set.less33
-rw-r--r--vendor/gipfl/web/public/css/43-inline-form.less29
-rw-r--r--vendor/gipfl/web/public/css/81-phpdiff.less97
-rw-r--r--vendor/gipfl/web/public/js/module.js69
-rw-r--r--vendor/gipfl/web/src/Form.php281
-rw-r--r--vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php158
-rw-r--r--vendor/gipfl/web/src/Form/Element/Boolean.php39
-rw-r--r--vendor/gipfl/web/src/Form/Element/MultiSelect.php119
-rw-r--r--vendor/gipfl/web/src/Form/Element/Password.php11
-rw-r--r--vendor/gipfl/web/src/Form/Element/TextWithActionButton.php104
-rw-r--r--vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php153
-rw-r--r--vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php16
-rw-r--r--vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php34
-rw-r--r--vendor/gipfl/web/src/Form/Validator/SimpleValidator.php27
-rw-r--r--vendor/gipfl/web/src/HtmlHelper.php29
-rw-r--r--vendor/gipfl/web/src/InlineForm.php10
-rw-r--r--vendor/gipfl/web/src/Table/NameValueTable.php47
-rw-r--r--vendor/gipfl/web/src/Widget/CollapsibleList.php74
-rw-r--r--vendor/gipfl/web/src/Widget/ConfigDiff.php106
-rw-r--r--vendor/gipfl/web/src/Widget/Hint.php45
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php179
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php82
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php230
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php143
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php163
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php128
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php87
-rw-r--r--vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php742
-rw-r--r--vendor/gipfl/zfdb/composer.json16
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Adapter.php1267
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Db2.php792
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Exception/AdapterException.php50
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionDb2.php42
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionMysqli.php30
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionOracle.php51
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionSqlsrv.php55
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Mysqli.php499
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Oracle.php595
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm.php352
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Db2.php215
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Ids.php290
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Mssql.php384
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Mysql.php258
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Oci.php367
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/PdoAdapter.php367
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Pgsql.php318
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Pdo/Sqlite.php293
-rw-r--r--vendor/gipfl/zfdb/src/Adapter/Sqlsrv.php640
-rw-r--r--vendor/gipfl/zfdb/src/Db.php248
-rw-r--r--vendor/gipfl/zfdb/src/Exception/DbException.php29
-rw-r--r--vendor/gipfl/zfdb/src/Exception/SelectException.php27
-rw-r--r--vendor/gipfl/zfdb/src/Expr.php70
-rw-r--r--vendor/gipfl/zfdb/src/Profiler.php463
-rw-r--r--vendor/gipfl/zfdb/src/Profiler/ProfilerException.php29
-rw-r--r--vendor/gipfl/zfdb/src/Profiler/ProfilerQuery.php206
-rw-r--r--vendor/gipfl/zfdb/src/Select.php1350
-rw-r--r--vendor/gipfl/zfdb/src/Statement.php462
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Db2Statement.php334
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Exception/StatementException.php48
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionDb2.php50
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionMysqli.php27
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionOracle.php49
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionSqlsrv.php55
-rw-r--r--vendor/gipfl/zfdb/src/Statement/MysqliStatement.php345
-rw-r--r--vendor/gipfl/zfdb/src/Statement/OracleStatement.php519
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Pdo/IbmPdoStatement.php86
-rw-r--r--vendor/gipfl/zfdb/src/Statement/Pdo/OciPdoStatement.php83
-rw-r--r--vendor/gipfl/zfdb/src/Statement/PdoStatement.php417
-rw-r--r--vendor/gipfl/zfdb/src/Statement/SqlsrvStatement.php423
-rw-r--r--vendor/gipfl/zfdb/src/Statement/StatementInterface.php198
-rw-r--r--vendor/gipfl/zfdbstore/composer.json16
-rw-r--r--vendor/gipfl/zfdbstore/src/BaseStore.php112
-rw-r--r--vendor/gipfl/zfdbstore/src/DbStorable.php79
-rw-r--r--vendor/gipfl/zfdbstore/src/DbStorableInterface.php36
-rw-r--r--vendor/gipfl/zfdbstore/src/NotFoundError.php9
-rw-r--r--vendor/gipfl/zfdbstore/src/Storable.php323
-rw-r--r--vendor/gipfl/zfdbstore/src/StorableInterface.php44
-rw-r--r--vendor/gipfl/zfdbstore/src/Store.php24
-rw-r--r--vendor/gipfl/zfdbstore/src/ZfDbStore.php241
297 files changed, 34708 insertions, 0 deletions
diff --git a/vendor/gipfl/calendar/composer.json b/vendor/gipfl/calendar/composer.json
new file mode 100644
index 0000000..5e84ef6
--- /dev/null
+++ b/vendor/gipfl/calendar/composer.json
@@ -0,0 +1,32 @@
+{
+ "name": "gipfl/calendar",
+ "type": "library",
+ "description": "Calendar Utils",
+ "keywords": ["calendar"],
+ "homepage": "https://github.com/gipfl/calendar",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "gipfl/format": ">=0.3",
+ "gipfl/icingaweb2": ">=0.4.0",
+ "gipfl/translation": ">=0.1.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Calendar\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "gipfl\\Tests\\Calendar\\": "tests"
+ }
+ }
+}
diff --git a/vendor/gipfl/calendar/src/Calendar.php b/vendor/gipfl/calendar/src/Calendar.php
new file mode 100644
index 0000000..e81227c
--- /dev/null
+++ b/vendor/gipfl/calendar/src/Calendar.php
@@ -0,0 +1,246 @@
+<?php
+
+namespace gipfl\Calendar;
+
+use gipfl\Format\LocalTimeFormat;
+use InvalidArgumentException;
+
+class Calendar
+{
+ const FIRST_IS_MONDAY = 1;
+ const FIRST_IS_SUNDAY = 0;
+
+ protected $firstOfWeek;
+
+ protected $weekDays = [];
+
+ protected $shortWeekDays = [];
+
+ protected $timeFormat;
+
+ public function __construct($firstOfWeek = self::FIRST_IS_MONDAY)
+ {
+ $this->timeFormat = new LocalTimeFormat();
+ $this->setFirstOfWeek($firstOfWeek);
+ }
+
+ public function firstOfWeekIsMonday()
+ {
+ return $this->firstOfWeek === self::FIRST_IS_MONDAY;
+ }
+
+ public function firstOfWeekIsSunday()
+ {
+ return $this->firstOfWeek === self::FIRST_IS_SUNDAY;
+ }
+
+ public function setFirstOfWeek($firstOfWeek)
+ {
+ if ($firstOfWeek === self::FIRST_IS_SUNDAY || $firstOfWeek === self::FIRST_IS_MONDAY) {
+ if ($firstOfWeek !== $this->firstOfWeek) {
+ $this->firstOfWeek = $firstOfWeek;
+ $this->prepareWeekDays();
+ }
+
+ return $this;
+ } else {
+ throw new InvalidArgumentException(
+ "First day of week has to be either 0 or 1, got '$firstOfWeek'"
+ );
+ }
+ }
+
+ protected function prepareWeekDays()
+ {
+ if ($this->firstOfWeekIsSunday()) {
+ $start = '2019-02-03';
+ } else {
+ $start = '2019-02-04';
+ }
+
+ for ($i = 0; $i < 7; $i++) {
+ $day = strtotime("$start +{$i}days");
+ $this->weekDays[] = $this->timeFormat->getWeekdayName($day);
+ $this->shortWeekDays[] = $this->timeFormat->getShortWeekdayName($day);
+ }
+ }
+
+ public function listWeekDayNames()
+ {
+ return $this->weekDays;
+ }
+
+ public function listShortWeekDayNames()
+ {
+ return $this->shortWeekDays;
+ }
+
+ /**
+ * Either 'N' or 'w', depending on the first day of week
+ *
+ * @return string
+ */
+ protected function getDowFormat()
+ {
+ if ($this->firstOfWeekIsMonday()) {
+ // N -> 1-7 (Mo-Su)
+ return 'N';
+ } else {
+ // w -> 0-6 (Su-Sa)
+ return 'w';
+ }
+ }
+
+ /**
+ * @param $time
+ * @return int
+ */
+ protected function getWeekDay($time)
+ {
+ return (int) date($this->getDowFormat(), $time);
+ }
+
+ /**
+ * @param int $now
+ * @return array
+ */
+ public function getDaysForWeek($now)
+ {
+ $formatDow = $this->getDowFormat();
+ $today = date('Y-m-d', $now);
+ $day = $this->getFirstDayOfWeek($today);
+ $weekday = (int) date($formatDow, strtotime($day));
+ $week = [$weekday => $day];
+ for ($i = 1; $i < 7; $i++) {
+ $day = date('Y-m-d', strtotime("$day +1day"));
+ $weekday = (int) date($formatDow, strtotime($day));
+ $week[$weekday] = $day;
+ }
+
+ return $week;
+ }
+
+ /**
+ * @param int $now
+ * @return array
+ */
+ public function getWorkingDaysForWeek($now)
+ {
+ $formatDow = $this->getDowFormat();
+ $today = date('Y-m-d', $now);
+ $day = $this->getFirstDayOfWeek($today, self::FIRST_IS_MONDAY);
+ $weekday = (int) date($formatDow, strtotime($day));
+ $week = [$weekday => $day];
+ for ($i = 1; $i < 5; $i++) {
+ $day = date('Y-m-d', strtotime("$day +1day"));
+ $weekday = (int) date($formatDow, strtotime($day));
+ $week[$weekday] = $day;
+ }
+
+ return $week;
+ }
+
+ /**
+ * @param string $day
+ * @param int $firstOfWeek
+ * @return string
+ */
+ public function getFirstDayOfWeek($day, $firstOfWeek = null)
+ {
+ if ($firstOfWeek === null) {
+ $firstOfWeek = $this->firstOfWeek;
+ }
+ $dow = $this->getWeekDay(strtotime($day));
+ if ($dow > $firstOfWeek) {
+ $sub = $dow - $firstOfWeek;
+ return date('Y-m-d', strtotime("$day -{$sub}day"));
+ } else {
+ return $day;
+ }
+ }
+
+ /**
+ * @param string $day
+ * @param int $firstOfWeek
+ * @return string
+ */
+ protected function getLastDayOfWeek($day, $firstOfWeek = null)
+ {
+ if ($firstOfWeek === null) {
+ $firstOfWeek = $this->firstOfWeek;
+ }
+ $dow = $this->getWeekDay(strtotime($day));
+ $lastOfWeek = $firstOfWeek + 6;
+ if ($dow < $lastOfWeek) {
+ $add = $lastOfWeek - $dow;
+ return static::expressionToDate(static::incDay($day, $add));
+ } else {
+ return $day;
+ }
+ }
+
+ public function getWeekOfTheYear($day)
+ {
+ $time = strtotime($day);
+ // 0 = Sunday
+ if ($this->firstOfWeekIsSunday() && $this->getWeekDay($time) === 0) {
+ if (substr($time, 4, 6) === '-12-31') {
+ return (int) date('W', strtotime(static::decDay($day)));
+ } else {
+ return (int) date('W', strtotime(static::incDay($day)));
+ }
+ } else {
+ return (int) date('W', $time);
+ }
+ }
+
+ /**
+ * @param int $now
+ * @return array
+ */
+ public function getWeeksForMonth($now)
+ {
+ $first = date('Y-m-01', $now);
+ $last = date('Y-m-d', strtotime("$first +1month -1day"));
+
+ $formatDow = $this->getDowFormat();
+ $end = $this->getLastDayOfWeek($last);
+ $day = $this->getFirstDayOfWeek($first);
+ $formerWeekOfTheYear = 0;
+ $weeks = [];
+ while ($day <= $end) {
+ $weekOfTheYear = $this->getWeekOfTheYear($day);
+ if ($weekOfTheYear !== $formerWeekOfTheYear) {
+ $weeks[$weekOfTheYear] = [];
+ $week = & $weeks[$weekOfTheYear];
+ }
+
+ $weekday = (int) date($formatDow, strtotime($day));
+ $week[$weekday] = $day;
+ $day = date('Y-m-d', strtotime("$day +1day"));
+ $formerWeekOfTheYear = $weekOfTheYear;
+ }
+
+ return $weeks;
+ }
+
+ protected static function expressionToDate($expression)
+ {
+ return date('Y-m-d', strtotime($expression));
+ }
+
+ /**
+ * @param string $day
+ * @param int $increment days to add
+ * @return string
+ */
+ protected static function incDay($day, $increment = 1)
+ {
+ return sprintf('%s +%dday', $day, $increment);
+ }
+
+ protected static function decDay($day, $decrement = 1)
+ {
+ return sprintf('%s -%dday', $day, $decrement);
+ }
+}
diff --git a/vendor/gipfl/calendar/src/Widget/CalendarMonth.php b/vendor/gipfl/calendar/src/Widget/CalendarMonth.php
new file mode 100644
index 0000000..2fe1c5b
--- /dev/null
+++ b/vendor/gipfl/calendar/src/Widget/CalendarMonth.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace gipfl\Calendar\Widget;
+
+use gipfl\Calendar\Calendar;
+use gipfl\Format\LocalTimeFormat;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\Translation\TranslationHelper;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlElement;
+
+/**
+ * WARNING: API will change
+ */
+class CalendarMonth extends BaseHtmlElement
+{
+ use TranslationHelper;
+
+ protected $tag = 'div';
+
+ protected $defaultAttributes = [
+ 'id' => 'calendar-wrap'
+ ];
+
+ /** @var Calendar */
+ protected $calendar;
+
+ /** @var int */
+ protected $now;
+
+ /** @var Url */
+ protected $url;
+
+ /** @var HtmlElement */
+ protected $days = [];
+
+ protected $timeFormatter;
+
+ public function __construct(Calendar $calendar, Url $url, $now)
+ {
+ $this->now = $now;
+ $this->url = $url;
+ $this->calendar = $calendar;
+ $this->timeFormatter = new LocalTimeFormat();
+ }
+
+ protected function dayRow()
+ {
+ return Html::tag('ul', ['class' => 'days']);
+ }
+
+ /**
+ * @param $day
+ * @return HtmlElement
+ */
+ protected function getDay($day)
+ {
+ $this->ensureAssembled();
+ return $this->days[$day];
+ }
+
+ protected function hasDay($day)
+ {
+ $this->ensureAssembled();
+
+ return isset($this->days[$day]);
+ }
+
+ protected function createDay($day)
+ {
+ $title = (int) substr($day, -2);
+
+ if ($title === 1) {
+ $title = sprintf(
+ '%d %s',
+ $title,
+ $this->timeFormatter->getShortMonthName(strtotime($day))
+ );
+ }
+ $li = Html::tag(
+ 'li',
+ ['class' => 'day'],
+ Html::tag('div', ['class' => 'date'], $title)
+ );
+
+ $this->days[$day] = $li;
+
+ return $li;
+ }
+
+ public function addEvent($time, $text)
+ {
+ $day = date('Y-m-d', $time);
+ if (! $this->hasDay($day)) {
+ return $this;
+ }
+ // $this->getDay($day)->add(Html::tag('div', ['class' => 'event'], [
+ $this->getDay($day)->add(Html::tag('a', ['class' => 'event', 'href' => '#'], [
+ Html::tag('div', [
+ 'class' => 'event-time',
+ 'title' => date('Y-m-d H:i:s')
+ ], date('H:i', $time)),
+ Html::tag('div', ['class' => 'event-desc'], $text)
+ ]));
+
+ return $this;
+ }
+
+ protected function getFormerMonth()
+ {
+ $first = date('Y-m-01', $this->now);
+
+ return date('Y-m-d', strtotime("$first -1month"));
+ }
+
+ protected function getNextMonth()
+ {
+ $first = date('Y-m-01', $this->now);
+
+ return date('Y-m-d', strtotime("$first +1month"));
+ }
+
+ protected function getNavigationLinks()
+ {
+ return Html::tag('div', ['class' => 'calendar-navigation'], [
+ Link::create('<', $this->url->with('day', $this->getFormerMonth())),
+ Link::create('>', $this->url->with('day', $this->getNextMonth())),
+ ]);
+ }
+
+ protected function assemble()
+ {
+ $now = $this->now;
+ $today = date('Y-m-d', $now);
+
+ $this->add(
+ Html::tag('header', [
+ $this->getNavigationLinks(),
+ Html::tag('h1', date('F Y', $now))
+ ])
+ );
+
+ $calendar = Html::tag('div', ['class' => 'calendar']);
+ $calendar->add($this->weekdaysHeader());
+ $thisMonth = substr($today, 0, 7);
+
+ foreach ($this->calendar->getWeeksForMonth($now) as $cw => $week) {
+ $weekRow = $this->dayRow();
+ $weekRow->add(
+ Html::tag('li', [
+ 'class' => 'weekName'
+ ], Html::tag('span', sprintf($this->translate('Week %s'), $cw)))
+ );
+ foreach ($week as $day) {
+ $weekRow->add($this->createDay($day));
+ if (substr($day, 0, 7) !== $thisMonth) {
+ $this->getDay($day)->addAttributes(['class' => 'other-month']);
+ }
+ }
+ $calendar->add($weekRow);
+ }
+
+ $this->add($calendar);
+ }
+
+ protected function weekdaysHeader()
+ {
+ $ul = Html::tag('ul', ['class' => 'weekdays']);
+ foreach ($this->calendar->listWeekDayNames() as $weekday) {
+ $ul->add(Html::tag('li', $this->translate($weekday)));
+ }
+
+ return $ul;
+ }
+}
diff --git a/vendor/gipfl/calendar/src/Widget/CalendarMonthSummary.php b/vendor/gipfl/calendar/src/Widget/CalendarMonthSummary.php
new file mode 100644
index 0000000..950530f
--- /dev/null
+++ b/vendor/gipfl/calendar/src/Widget/CalendarMonthSummary.php
@@ -0,0 +1,278 @@
+<?php
+
+namespace gipfl\Calendar\Widget;
+
+use gipfl\Calendar\Calendar;
+use gipfl\Format\LocalTimeFormat;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\Translation\TranslationHelper;
+use ipl\Html\HtmlElement;
+use ipl\Html\Table;
+
+class CalendarMonthSummary extends Table
+{
+ use TranslationHelper;
+
+ protected $defaultAttributes = [
+ 'data-base-target' => '_next',
+ 'class' => 'calendar',
+ ];
+
+ protected $today;
+
+ protected $year;
+
+ protected $month;
+
+ protected $strMonth;
+
+ protected $strToday;
+
+ protected $days = [];
+
+ /** @var Calendar|null */
+ protected $calendar;
+
+ protected $showWeekNumbers = true;
+
+ protected $showOtherMonth = false;
+
+ protected $showGrayFuture = true;
+
+ protected $title;
+
+ protected $color = '255, 128, 0';
+
+ protected $forcedMax;
+
+ protected $timeFormat;
+
+ public function __construct($year, $month)
+ {
+ $this->year = $year;
+ $this->month = $month;
+ $this->strMonth = sprintf('%d-%02d', $year, $month);
+ $this->strToday = date('Y-m-d');
+ $this->timeFormat = new LocalTimeFormat();
+ }
+
+ public function setBaseColorRgb($red, $green, $blue)
+ {
+ $this->color = sprintf('%d, %d, %d', $red, $green, $blue);
+
+ return $this;
+ }
+
+ public function setCalendar(Calendar $calendar)
+ {
+ $this->calendar = $calendar;
+
+ return $this;
+ }
+
+ public function getCalendar()
+ {
+ if ($this->calendar === null) {
+ $this->calendar = new Calendar();
+ }
+
+ return $this->calendar;
+ }
+
+ public function addEvents($events, Url $baseUrl)
+ {
+ if (empty($events)) {
+ return $this;
+ }
+
+ if ($this->forcedMax === null) {
+ $max = max($events);
+ } else {
+ $max = $this->forcedMax;
+ }
+
+ if ($max === 0 || $max === null) {
+ return $this;
+ }
+
+ foreach ($events as $day => $count) {
+ if (! $this->hasDay($day)) {
+ continue;
+ }
+
+ if (! $this->showOtherMonth && $this->dayIsInThisMonth($day)) {
+ continue;
+ }
+
+ $text = (int) substr($day, -2);
+
+ $link = Link::create($text, $baseUrl->with('day', $day));
+ $alpha = $count / $max;
+
+ if ($alpha > 0.4) {
+ $link->addAttributes(['style' => 'color: white;']);
+ }
+ $link->addAttributes([
+ 'title' => sprintf('%d events', $count),
+ 'style' => sprintf(
+ 'background-color: rgba(%s, %.2F);',
+ $this->color,
+ $alpha
+ )
+ ]);
+
+ $this->getDay($day)->setContent($link);
+ }
+
+ return $this;
+ }
+
+ public function markNow($now = null)
+ {
+ if ($now === null) {
+ $now = time();
+ }
+ $this->today = date('Y-m-d', $now);
+
+ return $this;
+ }
+
+ public function setTitle($title)
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ protected function getTitle()
+ {
+ if ($this->title === null) {
+ $this->title = $this->getMonthName() . ' ' . $this->year;
+ }
+
+ return $this->title;
+ }
+
+ public function forceMax($max)
+ {
+ $this->forcedMax = $max;
+
+ return $this;
+ }
+
+ protected function getMonthAsTimestamp()
+ {
+ return strtotime($this->strMonth . '-01');
+ }
+
+ protected function assemble()
+ {
+ $this->setCaption($this->getTitle());
+ $this->getHeader()->add($this->createWeekdayHeader());
+ $calendar = $this->getCalendar();
+ foreach ($calendar->getWeeksForMonth($this->getMonthAsTimestamp()) as $cw => $week) {
+ $weekRow = $this->weekRow($cw);
+ foreach ($week as $wDay => $day) {
+ $dayElement = $this->createDay($day);
+ $otherMonth = $this->dayIsInThisMonth($day);
+ if ($wDay < 1 || $wDay > 5) {
+ $dayElement->addAttributes(['class' => 'weekend']);
+ }
+ $weekRow->add($dayElement);
+ }
+ $this->add($weekRow);
+ }
+ }
+
+ /**
+ * @param $day
+ * @return HtmlElement
+ */
+ protected function getDay($day)
+ {
+ $this->ensureAssembled();
+
+ return $this->days[$day];
+ }
+
+ protected function hasDay($day)
+ {
+ $this->ensureAssembled();
+
+ return isset($this->days[$day]);
+ }
+
+ protected function dayIsInThisMonth($day)
+ {
+ return substr($day, 0, 7) !== $this->strMonth;
+ }
+
+ protected function createDay($day)
+ {
+ $otherMonth = $this->dayIsInThisMonth($day);
+ $title = (int) substr($day, -2);
+ if ($otherMonth && ! $this->showOtherMonth) {
+ $title = '';
+ }
+ $td = Table::td($title);
+ $this->days[$day] = $td;
+
+ if ($otherMonth) {
+ $td->addAttributes(['class' => 'other-month']);
+ } elseif ($this->showGrayFuture && $day > $this->strToday) {
+ $td->addAttributes(['class' => 'future-day']);
+ }
+
+ // TODO: today VS strToday?!
+ if ($day === $this->today) {
+ $td->addAttributes(['class' => 'today']);
+ }
+
+ return $td;
+ }
+
+ protected function weekRow($cw)
+ {
+ $row = Table::tr();
+
+ if ($this->showWeekNumbers) {
+ $row->add(Table::th(sprintf('%02d', $cw), [
+ 'title' => sprintf($this->translate('Calendar Week %d'), $cw)
+ ]));
+ }
+
+ return $row;
+ }
+
+ protected function getMonthName()
+ {
+ return $this->timeFormat->getMonthName($this->getMonthAsTimestamp());
+ }
+
+ protected function createWeekdayHeader()
+ {
+ $calendar = $this->getCalendar();
+ $cols = $calendar->listShortWeekDayNames();
+ $row = Table::tr();
+ if ($this->showWeekNumbers) {
+ $row->add(Table::th(''));
+ }
+ if ($calendar->firstOfWeekIsMonday()) {
+ $weekend = [6 => true, 7 => true];
+ } else {
+ $weekend = [1 => true, 7 => true];
+ }
+ $wDay = 0;
+ foreach ($cols as $day) {
+ $wDay++;
+ $col = Table::th($day);
+ if (isset($weekend[$wDay])) {
+ $col->addAttributes(['class' => 'weekend']);
+ }
+ $row->add($col);
+ }
+
+ return $row;
+ }
+}
diff --git a/vendor/gipfl/cli/composer.json b/vendor/gipfl/cli/composer.json
new file mode 100644
index 0000000..370beed
--- /dev/null
+++ b/vendor/gipfl/cli/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "gipfl/cli",
+ "description": "CLI utilities",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Cli\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ext-mbstring": "*",
+ "ext-pcntl": "*",
+ "ext-posix": "*",
+ "react/stream": ">=1.1",
+ "react/promise": "^2"
+ },
+ "require-dev": {
+ "react/child-process": ">=0.6"
+ }
+}
diff --git a/vendor/gipfl/cli/src/AnsiScreen.php b/vendor/gipfl/cli/src/AnsiScreen.php
new file mode 100644
index 0000000..2ae3f40
--- /dev/null
+++ b/vendor/gipfl/cli/src/AnsiScreen.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace gipfl\Cli;
+
+use InvalidArgumentException;
+
+/**
+ * Screen implementation for screens with ANSI escape code support
+ *
+ * @see http://en.wikipedia.org/wiki/ANSI_escape_code
+ */
+class AnsiScreen extends Screen
+{
+ const FG_COLORS = [
+ 'black' => '30',
+ 'darkgray' => '1;30',
+ 'red' => '31',
+ 'lightred' => '1;31',
+ 'green' => '32',
+ 'lightgreen' => '1;32',
+ 'brown' => '33',
+ 'yellow' => '1;33',
+ 'blue' => '34',
+ 'lightblue' => '1;34',
+ 'purple' => '35',
+ 'lightpurple' => '1;35',
+ 'cyan' => '36',
+ 'lightcyan' => '1;36',
+ 'lightgray' => '37',
+ 'white' => '1;37',
+ ];
+
+ const BG_COLORS = [
+ 'black' => '40',
+ 'red' => '41',
+ 'green' => '42',
+ 'brown' => '43',
+ 'blue' => '44',
+ 'purple' => '45',
+ 'cyan' => '46',
+ 'lightgray' => '47',
+ ];
+
+ /**
+ * Remove all ANSI escape codes from a given string
+ * @param $string
+ * @return string|string[]|null
+ */
+ public function stripAnsiCodes($string)
+ {
+ return \preg_replace('/\e\[?.*?[@-~]/', '', $string);
+ }
+
+ public function clear()
+ {
+ return "\033[2J" // Clear the whole screen
+ . "\033[1;1H" // Move the cursor to row 1, column 1
+ . "\033[1S"; // Scroll whole page up by 1 line (why?)
+ }
+
+ public function colorize($text, $fgColor = null, $bgColor = null)
+ {
+ return $this->startColor($fgColor, $bgColor)
+ . $text
+ . "\033[0m"; // Reset color codes
+ }
+
+ public function strlen($string)
+ {
+ return parent::strlen($this->stripAnsiCodes($string));
+ }
+
+ public function underline($text)
+ {
+ return "\033[4m"
+ . $text
+ . "\033[0m"; // Reset color codes
+ }
+
+ protected function fgColor($color)
+ {
+ if (! \array_key_exists($color, static::FG_COLORS)) {
+ throw new InvalidArgumentException(
+ "There is no such foreground color: $color"
+ );
+ }
+
+ return static::FG_COLORS[$color];
+ }
+
+ protected function bgColor($color)
+ {
+ if (! \array_key_exists($color, static::BG_COLORS)) {
+ throw new InvalidArgumentException(
+ "There is no such background color: $color"
+ );
+ }
+
+ return static::BG_COLORS[$color];
+ }
+
+ protected function startColor($fgColor = null, $bgColor = null)
+ {
+ $parts = [];
+ if ($fgColor !== null
+ && $bgColor !== null
+ && ! \array_key_exists($bgColor, static::BG_COLORS)
+ && \array_key_exists($bgColor, static::FG_COLORS)
+ && \array_key_exists($fgColor, static::BG_COLORS)
+ ) {
+ $parts[] = '7'; // reverse video, negative image
+ $parts[] = $this->bgColor($fgColor);
+ $parts[] = $this->fgColor($bgColor);
+ } else {
+ if ($fgColor !== null) {
+ $parts[] = $this->fgColor($fgColor);
+ }
+ if ($bgColor !== null) {
+ $parts[] = $this->bgColor($bgColor);
+ }
+ }
+ if (empty($parts)) {
+ return '';
+ }
+
+ return "\033[" . \implode(';', $parts) . 'm';
+ }
+}
diff --git a/vendor/gipfl/cli/src/Process.php b/vendor/gipfl/cli/src/Process.php
new file mode 100644
index 0000000..45c67b5
--- /dev/null
+++ b/vendor/gipfl/cli/src/Process.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace gipfl\Cli;
+
+class Process
+{
+ /** @var string|null */
+ protected static $initialCwd;
+
+ /**
+ * Set the command/process title for this process
+ *
+ * @param $title
+ */
+ public static function setTitle($title)
+ {
+ if (function_exists('cli_set_process_title')) {
+ \cli_set_process_title($title);
+ }
+ }
+
+ /**
+ * Replace this process with a new instance of itself by executing the
+ * very same binary with the very same parameters
+ */
+ public static function restart()
+ {
+ // _ is only available when executed via shell
+ $binary = static::getEnv('_');
+ $argv = $_SERVER['argv'];
+ if (\strlen($binary) === 0) {
+ // Problem: this doesn't work if we changed working directory and
+ // called the binary with a relative path. Something that doesn't
+ // happen when started as a daemon, and when started manually we
+ // should have $_ from our shell.
+ $binary = static::absoluteFilename(\array_shift($argv));
+ } else {
+ \array_shift($argv);
+ }
+ \pcntl_exec($binary, $argv, static::getEnv());
+ }
+
+ /**
+ * Get the given ENV variable, null if not available
+ *
+ * Returns an array with all ENV variables if no $key is given
+ *
+ * @param string|null $key
+ * @return array|string|null
+ */
+ public static function getEnv($key = null)
+ {
+ if ($key !== null) {
+ return \getenv($key);
+ }
+
+ if (PHP_VERSION_ID > 70100) {
+ return \getenv();
+ } else {
+ $env = $_SERVER;
+ unset($env['argv'], $env['argc']);
+
+ return $env;
+ }
+ }
+
+ /**
+ * Get the path to the executed binary when starting this command
+ *
+ * This fails if we changed working directory and called the binary with a
+ * relative path. Something that doesn't happen when started as a daemon.
+ * When started manually we should have $_ from our shell.
+ *
+ * To be always on the safe side please call Process::getInitialCwd() once
+ * after starting your process and before switching directory. That way we
+ * preserve our initial working directory.
+ *
+ * @return mixed|string
+ */
+ public static function getBinaryPath()
+ {
+ if (isset($_SERVER['_'])) {
+ return $_SERVER['_'];
+ } else {
+ global $argv;
+
+ return static::absoluteFilename($argv[0]);
+ }
+ }
+
+ /**
+ * The working directory as given by getcwd() the very first time we
+ * called this method
+ *
+ * @return string
+ */
+ public static function getInitialCwd()
+ {
+ if (self::$initialCwd === null) {
+ self::$initialCwd = \getcwd();
+ }
+
+ return self::$initialCwd;
+ }
+
+ /**
+ * Returns the absolute filename for the given file
+ *
+ * If relative, it's calculated in relation to the given working directory.
+ * The current working directory is being used if null is given.
+ *
+ * @param $filename
+ * @param null $cwd
+ * @return string
+ */
+ public static function absoluteFilename($filename, $cwd = null)
+ {
+ $filename = \str_replace(
+ DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR,
+ $filename
+ );
+ if ($filename[0] === '.') {
+ $filename = ($cwd ?: \getcwd()) . DIRECTORY_SEPARATOR . $filename;
+ }
+ $parts = \explode(DIRECTORY_SEPARATOR, $filename);
+ $result = [];
+ foreach ($parts as $part) {
+ if ($part === '.') {
+ continue;
+ }
+ if ($part === '..') {
+ \array_pop($result);
+ continue;
+ }
+ $result[] = $part;
+ }
+
+ return \implode(DIRECTORY_SEPARATOR, $result);
+ }
+}
diff --git a/vendor/gipfl/cli/src/Screen.php b/vendor/gipfl/cli/src/Screen.php
new file mode 100644
index 0000000..cb05a3f
--- /dev/null
+++ b/vendor/gipfl/cli/src/Screen.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace gipfl\Cli;
+
+/**
+ * Base class providing minimal CLI Screen functionality. While classes
+ * extending this one (read: AnsiScreen) should implement all the fancy cool
+ * things, this base class makes sure that your code will still run in
+ * environments with no ANSI or similar support
+ *
+ * ```php
+ * $screen = Screen::instance();
+ * echo $screen->center($screen->underline('Hello world'));
+ * ```
+ */
+class Screen
+{
+ protected $isUtf8;
+
+ /**
+ * Get a new Screen instance.
+ *
+ * For now this is limited to either a very basic Screen implementation as
+ * a fall-back or an AnsiScreen implementation with more functionality
+ *
+ * @return AnsiScreen|Screen
+ */
+ public static function factory()
+ {
+ if (! defined('STDOUT')) {
+ return new Screen();
+ }
+ if (\function_exists('posix_isatty') && \posix_isatty(STDOUT)) {
+ return new AnsiScreen();
+ } else {
+ return new Screen();
+ }
+ }
+
+ /**
+ * Center the given string horizontally on the current screen
+ *
+ * @param $string
+ * @return string
+ */
+ public function center($string)
+ {
+ $len = $this->strlen($string);
+ $width = (int) \floor(($this->getColumns() + $len) / 2) - $len;
+
+ return \str_repeat(' ', $width) . $string;
+ }
+
+ /**
+ * Clear the screen
+ *
+ * Impossible for non-ANSI screens, so let's output a newline for now
+ *
+ * @return string
+ */
+ public function clear()
+ {
+ return "\n";
+ }
+
+ /**
+ * Colorize the given text. Has no effect on a basic Screen, all colors
+ * will be accepted. It's prefectly legal to provide background or foreground
+ * only
+ *
+ * Returns the very same string, eventually enriched with related ANSI codes
+ *
+ * @param $text
+ * @param null $fgColor
+ * @param null $bgColor
+ *
+ * @return mixed
+ */
+ public function colorize($text, $fgColor = null, $bgColor = null)
+ {
+ return $text;
+ }
+
+ /**
+ * Generate $count newline characters
+ *
+ * @param int $count
+ * @return string
+ */
+ public function newlines($count = 1)
+ {
+ return \str_repeat(PHP_EOL, $count);
+ }
+
+ /**
+ * Calculate the visible length of a given string. While this is simple on
+ * a non-ANSI-screen, such implementation will be required to strip control
+ * characters to get the correct result
+ *
+ * @param $string
+ * @return int
+ */
+ public function strlen($string)
+ {
+ if ($this->isUtf8()) {
+ return \mb_strlen($string, 'UTF-8');
+ } else {
+ return \strlen($string);
+ }
+ }
+
+ /**
+ * Underline the given text - if possible
+ *
+ * @return string
+ */
+ public function underline($text)
+ {
+ return $text;
+ }
+
+ /**
+ * Get the number of currently available columns. Please note that this
+ * might chance at any time while your program is running
+ *
+ * @return int
+ */
+ public function getColumns()
+ {
+ $cols = (int) \getenv('COLUMNS');
+ if (! $cols) {
+ // stty -a ?
+ $cols = (int) \exec('tput cols');
+ }
+ if (! $cols) {
+ $cols = 80;
+ }
+
+ return $cols;
+ }
+
+ /**
+ * Get the number of currently available rows. Please note that this
+ * might chance at any time while your program is running
+ *
+ * @return int
+ */
+ public function getRows()
+ {
+ $rows = (int) \getenv('ROWS');
+ if (! $rows) {
+ // stty -a ?
+ $rows = (int) \exec('tput lines');
+ }
+ if (! $rows) {
+ $rows = 25;
+ }
+
+ return $rows;
+ }
+
+ /**
+ * Whether we're on a UTF-8 screen. We assume latin1 otherwise, there is no
+ * support for additional encodings
+ *
+ * @return bool
+ */
+ public function isUtf8()
+ {
+ if ($this->isUtf8 === null) {
+ // null should equal 0 here, however seems to equal '' on some systems:
+ $current = \setlocale(LC_ALL, 0);
+
+ $parts = explode(';', $current);
+ $lc_parts = [];
+ foreach ($parts as $part) {
+ if (\strpos($part, '=') === false) {
+ continue;
+ }
+ list($key, $val) = explode('=', $part, 2);
+ $lc_parts[$key] = $val;
+ }
+
+ $this->isUtf8 = \array_key_exists('LC_CTYPE', $lc_parts)
+ && \preg_match('~\.UTF-8$~i', $lc_parts['LC_CTYPE']);
+ }
+
+ return $this->isUtf8;
+ }
+}
diff --git a/vendor/gipfl/cli/src/Spinner.php b/vendor/gipfl/cli/src/Spinner.php
new file mode 100644
index 0000000..b949526
--- /dev/null
+++ b/vendor/gipfl/cli/src/Spinner.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace gipfl\Cli;
+
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Promise\ExtendedPromiseInterface;
+
+class Spinner
+{
+ const ASCII_SLASH = ['/', '-', '\\', '|'];
+ const ASCII_BOUNCING_CIRCLE = ['.', 'o', 'O', '°', 'O', 'o'];
+ const ROTATING_HALF_CIRCLE = ['◑', '◒', '◐', '◓'];
+ const ROTATING_EARTH = ['🌎', '🌏', '🌍'];
+ const ROTATING_MOON = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'];
+ const UP_DOWN_BAR = [' ', '_', '▁', '▃', '▄', '▅', '▆', '▇', '▆', '▅', '▄', '▃', '▁'];
+ const CLOCK = ['🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛'];
+ const WAVING_DOTS = ['⢄', '⢂', '⢁', '⡁', '⡈', '⡐', '⡠', '⡐', '⡈', '⡁', '⢁', '⢂'];
+ const ROTATING_DOTS = ['⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽', '⣾'];
+
+ /** @var LoopInterface */
+ protected $loop;
+
+ protected $frames;
+
+ protected $frame = -1;
+
+ protected $count;
+
+ protected $delay;
+
+ public function __construct(LoopInterface $loop, array $frames = self::ASCII_SLASH)
+ {
+ $this->loop = $loop;
+ $this->frames = $frames;
+ $this->count = \count($frames);
+ $this->delay = ((int) (2 * 100 / $this->count)) / 100;
+ }
+
+ protected function getNextFrame()
+ {
+ $first = $this->frame === -1;
+ $this->frame++;
+ if ($this->frame >= $this->count) {
+ $this->frame = 0;
+ }
+
+ return $this->frames[$this->frame];
+ }
+
+ public function spinWhile(ExtendedPromiseInterface $promise, callable $renderer)
+ {
+ $next = function () use ($renderer) {
+ $renderer($this->getNextFrame());
+ };
+ $spinTimer = $this->loop->addPeriodicTimer($this->delay, $next);
+ $deferred = new Deferred(function () use ($spinTimer) {
+ $this->loop->cancelTimer($spinTimer);
+ });
+ $this->loop->futureTick($next);
+ $wait = $deferred->promise();
+ $cancel = function () use ($wait) {
+ $wait->cancel();
+ };
+ $promise->otherwise($cancel)->then($cancel);
+
+ return $promise;
+ }
+}
diff --git a/vendor/gipfl/cli/src/Tty.php b/vendor/gipfl/cli/src/Tty.php
new file mode 100644
index 0000000..efe5924
--- /dev/null
+++ b/vendor/gipfl/cli/src/Tty.php
@@ -0,0 +1,132 @@
+<?php
+
+namespace gipfl\Cli;
+
+use InvalidArgumentException;
+use React\EventLoop\LoopInterface;
+use React\Stream\ReadableResourceStream;
+use React\Stream\WritableResourceStream;
+use RuntimeException;
+use function defined;
+use function fstat;
+use function function_exists;
+use function is_bool;
+use function is_resource;
+use function is_string;
+use function posix_isatty;
+use function register_shutdown_function;
+use function stream_isatty;
+use function stream_set_blocking;
+use function strlen;
+use function var_export;
+
+class Tty
+{
+ protected $stdin;
+
+ protected $stdout;
+
+ protected $loop;
+
+ protected $echo = true;
+
+ /** @var TtyMode */
+ protected $ttyMode;
+
+ public function __construct(LoopInterface $loop)
+ {
+ $this->loop = $loop;
+ register_shutdown_function([$this, 'restore']);
+ $loop->futureTick(function () {
+ $this->initialize();
+ });
+ }
+
+ public function setEcho($echo)
+ {
+ if (! is_bool($echo) && ! is_string($echo) && strlen($echo) !== 1) {
+ throw new InvalidArgumentException(
+ "\$echo must be boolean or a single character, got " . var_export($echo, 1)
+ );
+ }
+ $this->echo = $echo;
+ if ($this->ttyMode) {
+ if ($echo) {
+ $this->ttyMode->enableFeature('echo');
+ } else {
+ $this->ttyMode->disableFeature('echo');
+ }
+ }
+
+ return $this;
+ }
+
+ public function stdin()
+ {
+ if ($this->stdin === null) {
+ $this->assertValidStdin();
+ $this->stdin = new ReadableResourceStream(STDIN, $this->loop);
+ }
+
+ return $this->stdin;
+ }
+
+ protected function hasStdin()
+ {
+ return defined('STDIN') && is_resource(STDIN) && fstat(STDIN) !== false;
+ }
+
+ protected function assertValidStdin()
+ {
+ if (! $this->hasStdin()) {
+ throw new RuntimeException('I have no STDIN');
+ }
+ }
+
+ public function stdout()
+ {
+ if ($this->stdout === null) {
+ $this->assertValidStdout();
+ $this->stdout = new WritableResourceStream(STDOUT, $this->loop);
+ }
+
+ return $this->stdout;
+ }
+
+ protected function hasStdout()
+ {
+ return defined('STDOUT') && is_resource(STDOUT) && fstat(STDOUT) !== false;
+ }
+
+ protected function assertValidStdout()
+ {
+ if (! $this->hasStdout()) {
+ throw new RuntimeException('I have no STDOUT');
+ }
+ }
+
+ protected function initialize()
+ {
+ $this->ttyMode = new TtyMode();
+ $this->ttyMode->setPreferredMode($this->echo);
+ }
+
+ public static function isSupported()
+ {
+ if (PHP_VERSION_ID >= 70200) {
+ return stream_isatty(STDIN);
+ } elseif (function_exists('posix_isatty')) {
+ return posix_isatty(STDIN);
+ } else {
+ return false;
+ }
+ }
+
+ public function restore()
+ {
+ if ($this->hasStdin()) {
+ // ReadableResourceStream sets blocking to false, let's restore this
+ stream_set_blocking(STDIN, true);
+ }
+ }
+}
diff --git a/vendor/gipfl/cli/src/TtyMode.php b/vendor/gipfl/cli/src/TtyMode.php
new file mode 100644
index 0000000..8b9c884
--- /dev/null
+++ b/vendor/gipfl/cli/src/TtyMode.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace gipfl\Cli;
+
+use function escapeshellarg;
+use function register_shutdown_function;
+use function rtrim;
+use function shell_exec;
+
+class TtyMode
+{
+ protected $originalMode;
+
+ public function enableCanonicalMode()
+ {
+ $this->enableFeature('icanon');
+
+ return $this;
+ }
+
+ public function disableCanonicalMode()
+ {
+ $this->disableFeature('icanon');
+
+ return $this;
+ }
+
+ public function enableFeature(...$feature)
+ {
+ $this->preserve();
+ $cmd = 'stty ';
+ foreach ($feature as $f) {
+ $cmd .= escapeshellarg($f);
+ }
+
+ shell_exec($cmd);
+ }
+
+ public function disableFeature(...$feature)
+ {
+ $this->preserve();
+ $cmd = 'stty';
+ foreach ($feature as $f) {
+ $cmd .= ' -' . escapeshellarg($f);
+ }
+
+ shell_exec($cmd);
+ }
+
+ public function getCurrentMode()
+ {
+ return rtrim(shell_exec('stty -g'), PHP_EOL);
+ }
+
+ /**
+ * Helper allowing to call stty only once for the mose used flags, icanon and echo
+ * @param bool $echo
+ * @return $this
+ */
+ public function setPreferredMode($echo = true)
+ {
+ $this->preserve();
+ if ($echo) {
+ $this->disableFeature('icanon');
+ } else {
+ $this->disableFeature('icanon', 'echo');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @internal
+ */
+ public function preserve($force = false)
+ {
+ if ($force || $this->originalMode === null) {
+ $this->originalMode = $this->getCurrentMode();
+ register_shutdown_function([$this, 'restore']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @internal
+ */
+ public function restore()
+ {
+ if ($this->originalMode) {
+ shell_exec('stty ' . escapeshellarg($this->originalMode));
+ $this->originalMode = null;
+ }
+ }
+}
diff --git a/vendor/gipfl/curl/LICENSE b/vendor/gipfl/curl/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/curl/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/curl/composer.json b/vendor/gipfl/curl/composer.json
new file mode 100644
index 0000000..845793d
--- /dev/null
+++ b/vendor/gipfl/curl/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "gipfl/curl",
+ "description": "ReactPHP-friendly async CURL abstraction",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Curl\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.3",
+ "ext-curl": "*",
+ "guzzlehttp/psr7": ">=1.6",
+ "psr/http-message": "^1.0",
+ "react/event-loop": ">=1.0",
+ "react/promise": ">=2",
+ "react/stream": ">=1.0"
+ }
+}
diff --git a/vendor/gipfl/curl/src/CurlAsync.php b/vendor/gipfl/curl/src/CurlAsync.php
new file mode 100644
index 0000000..f9bd5d0
--- /dev/null
+++ b/vendor/gipfl/curl/src/CurlAsync.php
@@ -0,0 +1,338 @@
+<?php
+
+namespace gipfl\Curl;
+
+use Exception;
+use GuzzleHttp\Psr7\Message;
+use GuzzleHttp\Psr7\Request;
+use Psr\Http\Message\RequestInterface;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+use React\Promise\Deferred;
+use RuntimeException;
+use Throwable;
+use function array_shift;
+use function count;
+use function curl_close;
+use function curl_error;
+use function curl_multi_add_handle;
+use function curl_multi_close;
+use function curl_multi_exec;
+use function curl_multi_getcontent;
+use function curl_multi_info_read;
+use function curl_multi_init;
+use function curl_multi_remove_handle;
+use function curl_multi_select;
+use function curl_multi_setopt;
+use function curl_multi_strerror;
+
+/**
+ * This class provides an async CURL abstraction layer fitting into a ReactPHP
+ * reality, implemented based on curl_multi.
+ *
+ * As long as there are requests pending, a timer fires
+ *
+ */
+class CurlAsync
+{
+ const DEFAULT_POLLING_INTERVAL = 0.03;
+
+ /** @var false|resource */
+ protected $handle;
+
+ /** @var Deferred[] resourceIdx => Deferred */
+ protected $running = [];
+
+ /** @var [ [0 => resourceIdx, 1 => Deferred], ... ] */
+ protected $pending = [];
+
+ /** @var array[] resourceIdx => options */
+ protected $pendingOptions = [];
+
+ /** @var RequestInterface[] resourceIdx => RequestInterface */
+ protected $pendingRequests = [];
+
+ /** @var array resourceIdx => resource */
+ protected $curl = [];
+
+ /** @var int */
+ protected $maxParallelRequests = 30;
+
+ /** @var LoopInterface */
+ protected $loop;
+
+ /** @var float */
+ protected $fastInterval = self::DEFAULT_POLLING_INTERVAL;
+
+ /** @var TimerInterface */
+ protected $fastTimer;
+
+ /**
+ * @param LoopInterface $loop
+ */
+ public function __construct(LoopInterface $loop)
+ {
+ $this->loop = $loop;
+ $this->handle = curl_multi_init();
+ // Hint: I had no specific reason to disable pipelining, nothing but
+ // the desire to ease debugging. So, in case you feel confident you might
+ // want to remove this line
+ curl_multi_setopt($this->handle, CURLMOPT_PIPELINING, 0);
+ if (! $this->handle) {
+ throw new RuntimeException('Failed to initialize curl_multi');
+ }
+ }
+
+ public function get($url, $headers = [], $curlOptions = [])
+ {
+ return $this->send(new Request('GET', $url, $headers), $curlOptions);
+ }
+
+ public function post($url, $headers = [], $body = null, $curlOptions = [])
+ {
+ return $this->send(new Request('POST', $url, $headers, $body), $curlOptions);
+ }
+
+ public function head($url, $headers = [])
+ {
+ return $this->send(new Request('HEAD', $url, $headers));
+ }
+
+ public function send(RequestInterface $request, $curlOptions = [])
+ {
+ $curl = CurlHandle::createForRequest($request, $curlOptions);
+ $idx = (int) $curl;
+ $this->curl[$idx] = $curl;
+ $this->pendingOptions[$idx] = $curlOptions;
+ $this->pendingRequests[$idx] = $request;
+ $deferred = new Deferred(function () use ($idx) {
+ $this->freeByResourceReference($idx);
+ });
+ $this->pending[] = [$idx, $deferred];
+ $this->loop->futureTick(function () {
+ $this->enablePolling();
+ $this->enqueueNextRequestIfAny();
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @param int $max
+ * @return $this
+ */
+ public function setMaxParallelRequests($max)
+ {
+ $this->maxParallelRequests = (int) $max;
+
+ return $this;
+ }
+
+ public function getPendingCurlHandles()
+ {
+ return $this->curl;
+ }
+
+ protected function enqueueNextRequestIfAny()
+ {
+ while (count($this->pending) > 0 && count($this->running) < $this->maxParallelRequests) {
+ $next = array_shift($this->pending);
+ $resourceIdx = $next[0];
+ $this->running[$resourceIdx] = $next[1];
+ $curl = $this->curl[$resourceIdx];
+ // enqueued: curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
+ curl_multi_add_handle($this->handle, $curl);
+ }
+ }
+
+ public function rejectAllPendingRequests($reasonOrError = null)
+ {
+ $this->rejectAllRunningRequests($reasonOrError);
+ }
+
+ protected function rejectAllRunningRequests($reasonOrError = null)
+ {
+ $running = $this->running; // Hint: intentionally cloned
+ foreach ($running as $resourceNum => $deferred) {
+ $this->freeByResourceReference($resourceNum);
+ $deferred->reject($reasonOrError);
+ }
+ if (! empty($this->running)) {
+ throw new RuntimeException(
+ // Hint: should never be reached
+ 'All running requests should have been removed, but something has been left'
+ );
+ }
+ }
+
+ protected function rejectAllDeferredRequests($reasonOrError = null)
+ {
+ foreach ($this->pending as $pending) {
+ list($resourceNum, $deferred) = $pending;
+ $this->freeByResourceReference($resourceNum);
+ $deferred->reject($reasonOrError);
+ }
+
+ if (! empty($this->running)) {
+ throw new RuntimeException(
+ // Hint: should never be reached
+ 'All pending requests should have been removed, but something has been left'
+ );
+ }
+ }
+
+ /**
+ * Returns true in case at least one request completed
+ *
+ * @return bool
+ */
+ protected function checkForResults()
+ {
+ if (empty($this->running)) {
+ return false;
+ } else {
+ $handle = $this->handle;
+ do {
+ $status = curl_multi_exec($handle, $active);
+ } while ($status > 0);
+ // Hint: while ($status === CURLM_CALL_MULTI_PERFORM) ?
+
+ if ($status !== CURLM_OK) {
+ throw new RuntimeException(curl_multi_strerror($handle));
+ }
+ if ($active) {
+ $fds = curl_multi_select($handle, 0.01);
+ // We take no action here, we'll info_read anyways:
+ // $fds === -1 -> select failed, returning. Probably only happens when running out of FDs
+ // $fds === 0 -> Nothing to do
+ // TODO: figure how often we get here -> https://bugs.php.net/bug.php?id=61141
+ }
+ $gotResult = false;
+ while (false !== ($completed = curl_multi_info_read($handle))) {
+ $this->requestCompleted($handle, $completed);
+ if (empty($this->pending) && empty($this->running)) {
+ $this->disablePolling();
+ }
+ $gotResult = true;
+ }
+
+ return $gotResult;
+ }
+ }
+
+ protected function requestCompleted($handle, $completed)
+ {
+ $curl = $completed['handle'];
+ $resourceNum = (int) $curl; // Hint this is an object in PHP >= 8, a resource in older versions
+ $deferred = $this->running[$resourceNum];
+ $request = $this->pendingRequests[$resourceNum];
+ $options = $this->pendingOptions[$resourceNum];
+ $content = curl_multi_getcontent($curl);
+ curl_multi_remove_handle($handle, $curl);
+ $removeProxyHeaders = isset($options[CURLOPT_PROXYTYPE])
+ && $options[CURLOPT_PROXYTYPE] === CURLPROXY_HTTP
+ // We assume that CURLOPT_SUPPRESS_CONNECT_HEADERS has been set for the request
+ && !defined('CURLOPT_SUPPRESS_CONNECT_HEADERS');
+
+ if ($completed['result'] === CURLE_OK) {
+ $this->freeByResourceReference($resourceNum);
+ try {
+ $deferred->resolve($this->parseResponse($content, $removeProxyHeaders));
+ } catch (\Exception $e) {
+ $deferred->reject(new ResponseParseError($e->getMessage(), $request, null, $e->getCode(), $e));
+ }
+ } else {
+ try {
+ $deferred->resolve($this->parseResponse($content, $removeProxyHeaders));
+ } catch (\Exception $e) {
+ $response = null;
+ }
+ try {
+ $error = curl_error($curl);
+ if ($error === '') {
+ $error = 'Curl failed, but got no CURL error';
+ }
+ } catch (Throwable $e) {
+ $error = 'Unable to determine CURL error: ' . $e->getMessage();
+ } catch (Exception $e) {
+ $error = 'Unable to determine CURL error: ' . $e->getMessage();
+ }
+ $deferred->reject(new RequestError($error, $request, $response));
+ $this->freeByResourceReference($resourceNum);
+ }
+ }
+
+ protected function parseResponse($content, $stripProxyHeaders)
+ {
+ // This method can be removed once we support PHP 7.3+ only, as it
+ // has CURLOPT_SUPPRESS_CONNECT_HEADERS
+ $response = Message::parseResponse($content);
+ if ($stripProxyHeaders) {
+ $body = (string) $response->getBody();
+ if (preg_match('/^HTTP\/.*? [0-9]{3}[^\n]*\r?\n/s', $body)) {
+ // There is no such header on reused connections
+ $response = Message::parseResponse($body);
+ }
+ }
+
+ return $response;
+ }
+
+ /**
+ * Set the polling interval used while requests are pending. Defaults to
+ * self::DEFAULT_POLLING_INTERVAL
+ *
+ * @param float $interval
+ */
+ public function setInterval($interval)
+ {
+ if ($interval !== $this->fastInterval) {
+ $this->fastInterval = $interval;
+ $this->reEnableTimerIfActive();
+ }
+ }
+
+ protected function reEnableTimerIfActive()
+ {
+ if ($this->fastTimer !== null) {
+ $this->disablePolling();
+ $this->enablePolling();
+ }
+ }
+
+ /**
+ * Polling timer should be active only while requests are pending
+ */
+ protected function enablePolling()
+ {
+ if ($this->fastTimer === null) {
+ $this->fastTimer = $this->loop->addPeriodicTimer($this->fastInterval, function () {
+ if ($this->checkForResults()) {
+ $this->enqueueNextRequestIfAny();
+ }
+ });
+ }
+ }
+
+ protected function disablePolling()
+ {
+ if ($this->fastTimer) {
+ $this->loop->cancelTimer($this->fastTimer);
+ $this->fastTimer = null;
+ }
+ }
+
+ protected function freeByResourceReference($ref)
+ {
+ unset($this->pendingRequests[$ref]);
+ unset($this->pendingOptions[$ref]);
+ unset($this->running[$ref]);
+ curl_close($this->curl[$ref]);
+ unset($this->curl[$ref]);
+ }
+
+ public function __destruct()
+ {
+ curl_multi_close($this->handle);
+ }
+}
diff --git a/vendor/gipfl/curl/src/CurlHandle.php b/vendor/gipfl/curl/src/CurlHandle.php
new file mode 100644
index 0000000..d5309f0
--- /dev/null
+++ b/vendor/gipfl/curl/src/CurlHandle.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace gipfl\Curl;
+
+use Psr\Http\Message\RequestInterface;
+
+class CurlHandle
+{
+ protected static $curlOptions = [
+ CURLOPT_HEADER => true,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CONNECTTIMEOUT => 5,
+ CURLOPT_SSL_VERIFYPEER => true,
+ CURLOPT_SSL_VERIFYHOST => 2,
+ CURLOPT_ENCODING => 'gzip',
+ CURLOPT_TCP_NODELAY => true,
+ CURLINFO_HEADER_OUT => true,
+ CURLOPT_TCP_KEEPALIVE => 1,
+ CURLOPT_BUFFERSIZE => 512 * 1024,
+ ];
+
+ public static function createForRequest(RequestInterface $request, $curlOptions = [])
+ {
+ $headers = [];
+ foreach ($request->getHeaders() as $name => $values) {
+ foreach ($values as $value) {
+ $headers[] = "$name: $value";
+ }
+ }
+ $body = $request->getBody();
+ if ($body->getSize() > 0) {
+ $body = $body->getContents();
+ } else {
+ $body = null;
+ }
+
+
+ $curl = curl_init();
+ $opts = static::prepareCurlOptions(
+ $request->getMethod(),
+ (string) $request->getUri(),
+ $body,
+ $headers,
+ $curlOptions
+ );
+ curl_setopt_array($curl, $opts);
+
+ return $curl;
+ }
+
+ protected static function prepareCurlOptions($method, $url, $body = null, $headers = [], $curlOptions = [])
+ {
+ $opts = $curlOptions + [
+ CURLOPT_CUSTOMREQUEST => $method,
+ CURLOPT_URL => $url,
+ ] + self::$curlOptions;
+
+ if (isset($opts[CURLOPT_HTTPHEADER])) {
+ $opts[CURLOPT_HTTPHEADER] = array_merge($opts[CURLOPT_HTTPHEADER], $headers);
+ } else {
+ $opts[CURLOPT_HTTPHEADER] = $headers;
+ }
+ if (isset($opts[CURLOPT_PROXYTYPE])
+ && $opts[CURLOPT_PROXYTYPE] === CURLPROXY_HTTP
+ && defined('CURLOPT_SUPPRESS_CONNECT_HEADERS')
+ ) {
+ $opts[CURLOPT_SUPPRESS_CONNECT_HEADERS] = true;
+ }
+
+ if ($body !== null) {
+ $opts[CURLOPT_POSTFIELDS] = $body;
+ }
+
+ return $opts;
+ }
+}
diff --git a/vendor/gipfl/curl/src/RequestError.php b/vendor/gipfl/curl/src/RequestError.php
new file mode 100644
index 0000000..a65df3a
--- /dev/null
+++ b/vendor/gipfl/curl/src/RequestError.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\Curl;
+
+use Exception;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+class RequestError extends Exception // implements Psr\Http\Client\RequestExceptionInterface
+{
+ /** @var RequestInterface */
+ protected $request;
+
+ /** @var ResponseInterface */
+ protected $response;
+
+ public function __construct(
+ $message,
+ RequestInterface $request,
+ ?ResponseInterface $response = null,
+ $code = 0,
+ ?Exception $previous = null
+ ) {
+ parent::__construct($message, $code, $previous);
+ $this->request = $request;
+ $this->response = $response;
+ }
+
+ /**
+ * @return RequestInterface
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * @return ?ResponseInterface
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+}
diff --git a/vendor/gipfl/curl/src/ResponseParseError.php b/vendor/gipfl/curl/src/ResponseParseError.php
new file mode 100644
index 0000000..5f127b6
--- /dev/null
+++ b/vendor/gipfl/curl/src/ResponseParseError.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace gipfl\Curl;
+
+class ResponseParseError extends RequestError
+{
+}
diff --git a/vendor/gipfl/data-type/composer.json b/vendor/gipfl/data-type/composer.json
new file mode 100644
index 0000000..412c294
--- /dev/null
+++ b/vendor/gipfl/data-type/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "gipfl/data-type",
+ "description": "Serializable Data Types",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\DataType\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "gipfl/json": ">=0.2.0"
+ }
+}
diff --git a/vendor/gipfl/data-type/src/SetOfSettings.php b/vendor/gipfl/data-type/src/SetOfSettings.php
new file mode 100644
index 0000000..731a2d7
--- /dev/null
+++ b/vendor/gipfl/data-type/src/SetOfSettings.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace gipfl\DataType;
+
+use gipfl\Json\JsonSerialization;
+use gipfl\Json\JsonString;
+use function ksort;
+
+class SetOfSettings implements JsonSerialization
+{
+ /** @var Settings[] */
+ protected $sections = [];
+
+ /**
+ * @param Settings[]|array|\stdClass $set
+ */
+ public function __construct(array $set = [])
+ {
+ foreach ((array) $set as $section => $settings) {
+ $this->setSection($section, $settings);
+ }
+ }
+
+ public static function fromSerialization($any)
+ {
+ return new static($any);
+ }
+
+ public function set($section, $setting, $value)
+ {
+ if (! isset($this->sections[$section])) {
+ $this->sections[$section] = new Settings();
+ }
+ $this->sections[$section]->set($setting, $value);
+
+ return $this;
+ }
+
+ public function get($section, $setting, $default = null)
+ {
+ if (isset($this->sections[$section])) {
+ return $this->sections[$section]->get($setting, $default);
+ }
+
+ return $default;
+ }
+
+ public function setSection($section, $settings)
+ {
+ if ($settings instanceof Settings) {
+ $this->sections[$section] = clone($settings);
+ } else {
+ $this->sections[$section] = new Settings($settings);
+ }
+
+ return $this;
+ }
+
+ public function cloneSection($section)
+ {
+ if (array_key_exists($section, $this->sections)) {
+ return clone($this->sections[$section]);
+ }
+
+ return new Settings();
+ }
+
+ public function equals(SetOfSettings $settings)
+ {
+ return JsonString::encode($settings) === JsonString::encode($this);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ ksort($this->sections);
+ return (object) $this->sections;
+ }
+}
diff --git a/vendor/gipfl/data-type/src/Settings.php b/vendor/gipfl/data-type/src/Settings.php
new file mode 100644
index 0000000..64b179d
--- /dev/null
+++ b/vendor/gipfl/data-type/src/Settings.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace gipfl\DataType;
+
+use gipfl\Json\JsonSerialization;
+use gipfl\Json\JsonString;
+use gipfl\Json\SerializationHelper;
+use InvalidArgumentException;
+use stdClass;
+use function array_key_exists;
+use function ksort;
+
+class Settings implements JsonSerialization
+{
+ protected $settings = [];
+
+ /**
+ * @param object|array $settings
+ */
+ public function __construct($settings = [])
+ {
+ foreach ((array) $settings as $property => $value) {
+ $this->set($property, $value);
+ }
+ }
+
+ /**
+ * @param stdClass|array $object
+ * @return static
+ */
+ public static function fromSerialization($object)
+ {
+ return new static($object);
+ }
+
+ public function set($name, $value)
+ {
+ SerializationHelper::assertSerializableValue($value);
+ $this->settings[$name] = $value;
+ }
+
+ public function get($name, $default = null)
+ {
+ if ($this->has($name)) {
+ return $this->settings[$name];
+ }
+
+ return $default;
+ }
+
+ public function getArray($name, $default = [])
+ {
+ if ($this->has($name)) {
+ return (array) $this->settings[$name];
+ }
+
+ return $default;
+ }
+
+ public function requireArray($name)
+ {
+ return (array) $this->getRequired(($name));
+ }
+
+ public function getAsSettings($name, Settings $default = null)
+ {
+ if ($this->has($name)) {
+ return Settings::fromSerialization($this->settings[$name]);
+ }
+
+ if ($default === null) {
+ return new Settings();
+ }
+
+ return $default;
+ }
+
+ public function getRequired($name)
+ {
+ if ($this->has($name)) {
+ return $this->settings[$name];
+ }
+
+ throw new InvalidArgumentException("Setting '$name' is not available");
+ }
+
+ public function has($name)
+ {
+ return array_key_exists($name, $this->settings);
+ }
+
+ public function equals(Settings $settings)
+ {
+ return JsonString::encode($settings) === JsonString::encode($this);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ ksort($this->settings);
+ return (object) $this->settings;
+ }
+}
diff --git a/vendor/gipfl/db-migration/composer.json b/vendor/gipfl/db-migration/composer.json
new file mode 100644
index 0000000..da2ae4a
--- /dev/null
+++ b/vendor/gipfl/db-migration/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "gipfl/db-migration",
+ "description": "Simple DB migration helper",
+ "type": "library",
+ "require": {
+ "php": ">=5.6"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\DbMigration\\": "src"
+ }
+ }
+}
diff --git a/vendor/gipfl/db-migration/src/Migration.php b/vendor/gipfl/db-migration/src/Migration.php
new file mode 100644
index 0000000..2e6c586
--- /dev/null
+++ b/vendor/gipfl/db-migration/src/Migration.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace gipfl\DbMigration;
+
+use Exception;
+use gipfl\ZfDb\Adapter\Pdo\PdoAdapter as Db;
+use InvalidArgumentException;
+use RuntimeException;
+use Zend_Db_Adapter_Pdo_Abstract as ZfDb;
+
+class Migration
+{
+ /**
+ * @var string
+ */
+ protected $sql;
+
+ /**
+ * @var int
+ */
+ protected $version;
+
+ public function __construct($version, $sql)
+ {
+ $this->version = $version;
+ $this->sql = $sql;
+ }
+
+ /**
+ * @param Db|ZfDb $db
+ * @return $this
+ */
+ public function apply($db)
+ {
+ if (! ($db instanceof Db || $db instanceof ZfDb)) {
+ throw new InvalidArgumentException('$db must be an valid Zend_Db PDO adapter');
+ }
+ // TODO: this is fragile and depends on accordingly written schema files:
+ $sql = preg_replace('/-- .*$/m', '', $this->sql);
+ $queries = preg_split(
+ '/[\n\s\t]*;[\n\s\t]+/s',
+ $sql,
+ -1,
+ PREG_SPLIT_NO_EMPTY
+ );
+
+ if (empty($queries)) {
+ throw new RuntimeException(sprintf(
+ 'Migration %d has no queries',
+ $this->version
+ ));
+ }
+
+ try {
+ foreach ($queries as $query) {
+ if (preg_match('/^(?:OPTIMIZE|EXECUTE) /i', $query)) {
+ $db->query($query);
+ } else {
+ $db->exec($query);
+ }
+ }
+ } catch (Exception $e) {
+ throw new RuntimeException(sprintf(
+ 'Migration %d failed (%s) while running %s',
+ $this->version,
+ $e->getMessage(),
+ $query
+ ));
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/db-migration/src/Migrations.php b/vendor/gipfl/db-migration/src/Migrations.php
new file mode 100644
index 0000000..2f85aa4
--- /dev/null
+++ b/vendor/gipfl/db-migration/src/Migrations.php
@@ -0,0 +1,299 @@
+<?php
+
+namespace gipfl\DbMigration;
+
+use DirectoryIterator;
+use Exception;
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Adapter\Adapter as Db;
+use gipfl\ZfDb\Adapter\Pdo\Mysql;
+use gipfl\ZfDb\Adapter\Pdo\Pgsql;
+use InvalidArgumentException;
+use Zend_Db_Adapter_Pdo_Abstract as ZfDb;
+use Zend_Db_Adapter_Pdo_Mysql as ZfMysql;
+use Zend_Db_Adapter_Pdo_Pgsql as ZfPgsql;
+
+class Migrations
+{
+ const DB_TYPE_MYSQL = 'mysql';
+
+ const DB_TYPE_POSTGRESQL = 'pgsql';
+
+ /** @var Db */
+ protected $db;
+
+ /** @var string mysql or pgsql */
+ protected $dbType;
+
+ /** @var string */
+ protected $schemaDirectory;
+
+ /** @var string */
+ protected $tableName;
+
+ /**
+ * Migrations constructor.
+ *
+ * @param Db|ZfDb $db
+ * @param string $schemaDirectory
+ * @param string $tableName
+ */
+ public function __construct($db, $schemaDirectory, $tableName = 'schema_migration')
+ {
+ if (! ($db instanceof Db || $db instanceof ZfDb)) {
+ throw new InvalidArgumentException('$db must be an valid Zend_Db PDO adapter');
+ }
+ $this->db = $db;
+ if ($db instanceof Mysql || $db instanceof ZfMysql) {
+ $this->dbType = self::DB_TYPE_MYSQL;
+ } elseif ($db instanceof Pgsql || $db instanceof ZfPgsql) {
+ $this->dbType = self::DB_TYPE_POSTGRESQL;
+ } else {
+ throw new InvalidArgumentException(sprintf(
+ 'Migrations are currently supported for MySQL and PostgreSQL only, got %s',
+ get_class($db)
+ ));
+ }
+ $this->tableName = (string) $tableName;
+ $this->schemaDirectory = (string) $schemaDirectory;
+ }
+
+ /**
+ * Still unused
+ *
+ * @throws AdapterException|\Zend_Db_Adapter_Exception
+ */
+ protected function createMigrationsTable()
+ {
+ if ($this->dbType === self::DB_TYPE_POSTGRESQL) {
+ $create = /** @lang text */
+ <<<SQL
+
+CREATE TABLE {$this->tableName} (
+ schema_version SMALLINT NOT NULL,
+ migration_time TIMESTAMP WITH TIME ZONE NOT NULL,
+ PRIMARY KEY (schema_version)
+);
+
+SQL;
+ } else {
+ $create = /** @lang text */
+ <<<SQL
+CREATE TABLE {$this->tableName} (
+ schema_version SMALLINT UNSIGNED NOT NULL,
+ migration_time DATETIME NOT NULL,
+ PRIMARY KEY (schema_version)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
+SQL;
+ }
+ $this->db->exec($create);
+ }
+
+ /**
+ * @return int
+ */
+ public function getLastMigrationNumber()
+ {
+ try {
+ $query = $this->db->select()->from(
+ ['m' => $this->getTableName()],
+ ['schema_version' => 'MAX(schema_version)']
+ );
+
+ return (int) $this->db->fetchOne($query);
+ } catch (Exception $e) {
+ return 0;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ protected function getTableName()
+ {
+ return $this->tableName;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasAnyTable()
+ {
+ return count($this->db->listTables()) > 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasTable($tableName)
+ {
+ return in_array($tableName, $this->db->listTables());
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasMigrationsTable()
+ {
+ return $this->hasTable($this->tableName);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasSchema()
+ {
+ return $this->listPendingMigrations() !== [0];
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasPendingMigrations()
+ {
+ return $this->countPendingMigrations() > 0;
+ }
+
+ /**
+ * @return int
+ */
+ public function countPendingMigrations()
+ {
+ return count($this->listPendingMigrations());
+ }
+
+ /**
+ * @return Migration[]
+ */
+ public function getPendingMigrations()
+ {
+ $migrations = array();
+ foreach ($this->listPendingMigrations() as $version) {
+ $migrations[] = new Migration(
+ $version,
+ $this->loadMigrationFile($version)
+ );
+ }
+
+ return $migrations;
+ }
+
+ /**
+ * @return $this
+ */
+ public function applyPendingMigrations()
+ {
+ foreach ($this->getPendingMigrations() as $migration) {
+ $migration->apply($this->db);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return int[]
+ */
+ public function listPendingMigrations()
+ {
+ $lastMigration = $this->getLastMigrationNumber();
+ if ($lastMigration === 0) {
+ return [0];
+ }
+
+ return $this->listMigrationsAfter($this->getLastMigrationNumber());
+ }
+
+ /**
+ * @return int[]
+ */
+ public function listAllMigrations()
+ {
+ $dir = $this->getMigrationsDirectory();
+ $versions = [];
+
+ if (! is_readable($dir)) {
+ return $versions;
+ }
+
+ foreach (new DirectoryIterator($dir) as $file) {
+ if ($file->isDot()) {
+ continue;
+ }
+
+ $filename = $file->getFilename();
+ if (preg_match('/^upgrade_(\d+)\.sql$/', $filename, $match)) {
+ $versions[] = (int) $match[1];
+ }
+ }
+
+ sort($versions);
+
+ return $versions;
+ }
+
+ /**
+ * @param $version
+ * @return false|string
+ */
+ public function loadMigrationFile($version)
+ {
+ if ($version === 0) {
+ $filename = $this->getFullSchemaFile();
+ } else {
+ $filename = sprintf(
+ '%s/upgrade_%d.sql',
+ $this->getMigrationsDirectory(),
+ $version
+ );
+ }
+
+ return file_get_contents($filename);
+ }
+
+ /**
+ * @param $version
+ * @return int[]
+ */
+ protected function listMigrationsAfter($version)
+ {
+ $filtered = [];
+ foreach ($this->listAllMigrations() as $available) {
+ if ($available > $version) {
+ $filtered[] = $available;
+ }
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * @param ?string $subDirectory
+ * @return string
+ */
+ public function getSchemaDirectory($subDirectory = null)
+ {
+ if ($subDirectory === null) {
+ return $this->schemaDirectory;
+ } else {
+ return $this->schemaDirectory . '/' . ltrim($subDirectory, '/');
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getMigrationsDirectory()
+ {
+ return $this->getSchemaDirectory($this->dbType . '-migrations');
+ }
+
+ /**
+ * @return string
+ */
+ protected function getFullSchemaFile()
+ {
+ return $this->getSchemaDirectory(
+ $this->dbType. '.sql'
+ );
+ }
+}
diff --git a/vendor/gipfl/diff/LICENSE b/vendor/gipfl/diff/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/diff/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/diff/composer.json b/vendor/gipfl/diff/composer.json
new file mode 100644
index 0000000..05c12a7
--- /dev/null
+++ b/vendor/gipfl/diff/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "gipfl/diff",
+ "description": "php-diff wrapper supporting ipl/html",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Diff\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ext-mbstring": "*",
+ "ipl/html": ">=0.2"
+ }
+}
diff --git a/vendor/gipfl/diff/public/css/diff.less b/vendor/gipfl/diff/public/css/diff.less
new file mode 100644
index 0000000..1dc2ef6
--- /dev/null
+++ b/vendor/gipfl/diff/public/css/diff.less
@@ -0,0 +1,133 @@
+@color-diff-ins: @color-ok;
+@color-diff-del: @color-critical;
+@color-diff-changed-old: fade(@color-critical, 30%);
+@color-diff-changed-new: fade(@color-ok, 30%);
+@color-diff-green-shade-light: #dfd;
+@color-diff-pale-green: #9e9;
+@color-diff-text-on-diff: #051030;
+@color-change-replace-del: #e99;
+
+.Differences {
+ width: 100%;
+ table-layout: fixed;
+ empty-cells: show;
+}
+
+.Differences thead {
+ display: none;
+}
+
+.Differences thead th {
+ text-align: left;
+ padding-left: 4 / 14 * 16em;
+}
+
+.Differences tbody th {
+ text-align: right;
+ width: 4em;
+ padding: 1px 2px;
+ border-right: 1px solid @gray-light;
+ background: @gray-lightest;
+ font-weight: normal;
+ vertical-align: top;
+}
+
+.Differences tbody td {
+ color: @text-color;
+ width: 50%;
+ .preformatted();
+ word-break: break-all;
+}
+
+.DifferencesSideBySide {
+ ins, del {
+ text-decoration: none;
+ }
+
+ .ChangeInsert {
+ td.Left {
+ background: @gray-lighter;
+ }
+ td.Right {
+ background: @color-diff-changed-new;
+ color: @color-diff-text-on-diff;
+ }
+ }
+
+ .ChangeDelete {
+ td.Left {
+ background: @color-diff-changed-old;
+ color: @color-diff-text-on-diff;
+ }
+ td.Right {
+ background: @gray-lighter;
+ }
+ }
+
+ .ChangeReplace {
+ td.Left {
+ background: @color-diff-changed-old;
+ color: @color-diff-text-on-diff;
+ del {
+ background: @color-diff-del;
+ }
+ }
+
+ td.Right {
+ background: @color-diff-changed-new;
+ color: @color-diff-text-on-diff;
+ ins {
+ background: @color-diff-ins;
+ }
+ }
+
+ }
+}
+
+.Differences .Skipped {
+ background: @gray-lightest;
+}
+
+.DifferencesInline .ChangeReplace .Left,
+.DifferencesInline .ChangeDelete .Left {
+ background: @color-diff-changed-old;
+}
+
+.DifferencesInline .ChangeReplace .Right,
+.DifferencesInline .ChangeInsert .Right {
+ background: @color-diff-green-shade-light;
+}
+
+.DifferencesInline .ChangeReplace ins {
+ background: @color-diff-pale-green;
+}
+
+.DifferencesInline .ChangeReplace del {
+ background: @color-change-replace-del;
+}
+
+.DifferencesInline {
+ tr td:last-child {
+ width: 90%;
+ }
+ tr th:first-child {
+ width: 5%;
+ }
+ tr th:nth-child(2) {
+ width: 5%;
+ }
+ ins, del {
+ text-decoration: none;
+ }
+}
+
+#layout.compact-layout, #layout.default-layout {
+ &.twocols table.Differences {
+ th {
+ font-size: 0.75em;
+ }
+ td {
+ font-size: 0.916em;
+ }
+ }
+}
diff --git a/vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php b/vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php
new file mode 100644
index 0000000..5df8bc0
--- /dev/null
+++ b/vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace gipfl\Diff\HtmlRenderer;
+
+use gipfl\Diff\PhpDiff\Renderer\Html\Inline;
+use ipl\Html\ValidHtml;
+
+class InlineDiff extends Inline implements ValidHtml
+{
+}
diff --git a/vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php b/vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php
new file mode 100644
index 0000000..e2ac5b2
--- /dev/null
+++ b/vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace gipfl\Diff\HtmlRenderer;
+
+use gipfl\Diff\PhpDiff\Renderer\Html\SideBySide;
+use ipl\Html\ValidHtml;
+
+class SideBySideDiff extends SideBySide implements ValidHtml
+{
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff.php b/vendor/gipfl/diff/src/PhpDiff.php
new file mode 100644
index 0000000..da2cb1f
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace gipfl\Diff;
+
+use gipfl\Diff\PhpDiff\OpCodeHelper;
+use gipfl\Diff\PhpDiff\SequenceMatcher;
+
+class PhpDiff
+{
+ /** @var array The "old" sequence to use as the basis for the comparison */
+ private $left;
+
+ /** @var array The "new" sequence to generate the changes for */
+ private $right;
+
+ /** @var array contains the generated opcodes for the differences between the two items */
+ private $groupedCodes;
+
+ /**
+ * @var array Associative array of the default options available for the diff class and their default value.
+ */
+ private $defaultOptions = [
+ 'context' => 3,
+ 'ignoreNewLines' => false,
+ 'ignoreWhitespace' => false,
+ 'ignoreCase' => false
+ ];
+
+ /**
+ * @var array Array of the options that have been applied for generating the diff.
+ */
+ private $options;
+
+ /**
+ * $left and $right can be strings, arrays of lines, null or any object that
+ * can be casted to a string
+ *
+ * @param mixed $left Left hand (old) side of the comparison
+ * @param mixed $right Right hand (new) side of the comparison
+ * @param array $options see $defaultOptions for possible settings
+ */
+ public function __construct($left, $right, array $options = [])
+ {
+ $this->setLeftLines($this->wantArray($left));
+ $this->setRightLines($this->wantArray($right));
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+
+ /**
+ * Get a range of lines from $start to $end from the first comparison string
+ * and return them as an array. If no values are supplied, the entire string
+ * is returned. It's also possible to specify just one line to return only
+ * that line.
+ *
+ * @param int $start The starting number.
+ * @param int $end The ending number. If not supplied, only the item in $start will be returned.
+ * @return array Array of all of the lines between the specified range.
+ */
+ public function getLeft($start = 0, $end = null)
+ {
+ if ($start === 0 && $end === null) {
+ return $this->left;
+ }
+
+ if ($end === null) {
+ $length = 1;
+ } else {
+ $length = $end - $start;
+ }
+
+ return array_slice($this->left, $start, $length);
+ }
+
+ /**
+ * Get a range of lines from $start to $end from the second comparison string
+ * and return them as an array. If no values are supplied, the entire string
+ * is returned. It's also possible to specify just one line to return only
+ * that line.
+ *
+ * @param int $start The starting number.
+ * @param int $end The ending number. If not supplied, only the item in $start will be returned.
+ * @return array Array of all of the lines between the specified range.
+ */
+ public function getRight($start = 0, $end = null)
+ {
+ if ($start === 0 && $end === null) {
+ return $this->right;
+ }
+
+ if ($end === null) {
+ $length = 1;
+ } else {
+ $length = $end - $start;
+ }
+
+ return array_slice($this->right, $start, $length);
+ }
+
+ /**
+ * Generate a list of the compiled and grouped opcodes for the differences between the
+ * two strings. Generally called by the renderer, this class instantiates the sequence
+ * matcher and performs the actual diff generation and return an array of the opcodes
+ * for it. Once generated, the results are cached in the diff class instance.
+ *
+ * @return array Array of the grouped opcodes for the generated diff.
+ */
+ public function getGroupedOpcodes()
+ {
+ if ($this->groupedCodes === null) {
+ $this->groupedCodes = $this->fetchGroupedOpCodes();
+ }
+
+ return $this->groupedCodes;
+ }
+
+ protected function fetchGroupedOpCodes()
+ {
+ $matcher = new SequenceMatcher($this->left, $this->right, null, $this->options);
+ return OpCodeHelper::getGroupedOpcodes(
+ $matcher->getOpcodes(),
+ $this->options['context']
+ );
+ }
+
+ protected function wantArray($value)
+ {
+ if (empty($value)) {
+ return [];
+ }
+ if (! is_array($value)) {
+ return explode("\n", (string) $value);
+ }
+
+ return $value;
+ }
+
+ protected function setLeftLines(array $lines)
+ {
+ $this->left = $lines;
+ }
+
+ protected function setRightLines(array $lines)
+ {
+ $this->right = $lines;
+ }
+
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php b/vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php
new file mode 100644
index 0000000..2c57c8d
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff;
+
+abstract class ArrayHelper
+{
+ /**
+ * Helper function that provides the ability to return the value for a key
+ * in an array of it exists, or if it doesn't then return a default value.
+ * Essentially cleaner than doing a series of if(isset()) {} else {} calls.
+ *
+ * @param array $array The array to search.
+ * @param string $key The key to check that exists.
+ * @param mixed $default The value to return as the default value if the key doesn't exist.
+ * @return mixed The value from the array if the key exists or otherwise the default.
+ */
+ public static function getPropertyOrDefault($array, $key, $default)
+ {
+ if (isset($array[$key])) {
+ return $array[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks
+ *
+ * @param array $a First array to compare.
+ * @param array $b Second array to compare.
+ * @return int -1, 0 or 1, as expected by the usort function.
+ */
+ public static function tupleSort($a, $b)
+ {
+ $max = max(count($a), count($b));
+ for ($i = 0; $i < $max; ++$i) {
+ if ($a[$i] < $b[$i]) {
+ return -1;
+ }
+ if ($a[$i] > $b[$i]) {
+ return 1;
+ }
+ }
+
+ if (count($a) === count($b)) {
+ return 0;
+ }
+ if (count($a) < count($b)) {
+ return -1;
+ }
+
+ return 1;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php b/vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php
new file mode 100644
index 0000000..7b12b1e
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff;
+
+use function count;
+use function max;
+use function min;
+
+abstract class OpCodeHelper
+{
+ /**
+ * Return a list of all of the opcodes for the differences between the
+ * two strings.
+ *
+ * The nested array returned contains an array describing the opcode
+ * which includes:
+ * 0 - The type of tag (as described below) for the opcode.
+ * 1 - The beginning line in the first sequence.
+ * 2 - The end line in the first sequence.
+ * 3 - The beginning line in the second sequence.
+ * 4 - The end line in the second sequence.
+ *
+ * The different types of tags include:
+ * replace - The string from $i1 to $i2 in $a should be replaced by
+ * the string in $b from $j1 to $j2.
+ * delete - The string in $a from $i1 to $j2 should be deleted.
+ * insert - The string in $b from $j1 to $j2 should be inserted at
+ * $i1 in $a.
+ * equal - The two strings with the specified ranges are equal.
+ *
+ * @param array $blocks
+ * @return array Array of the opcodes describing the differences between the strings.
+ */
+ public static function calculateOpCodes(array $blocks)
+ {
+ $lastLeftEnd = 0;
+ $lastRightEnd = 0;
+ $opCodes = [];
+
+ foreach ($blocks as list($beginLeft, $beginRight, $cntLines)) {
+ $tag = null;
+ if ($lastLeftEnd < $beginLeft) {
+ if ($lastRightEnd < $beginRight) {
+ $tag = 'replace';
+ } else {
+ $tag = 'delete';
+ }
+ } elseif ($lastRightEnd < $beginRight) {
+ $tag = 'insert';
+ }
+
+ if ($tag) {
+ $opCodes[] = [$tag, $lastLeftEnd, $beginLeft, $lastRightEnd, $beginRight];
+ }
+
+ $lastLeftEnd = $beginLeft + $cntLines;
+ $lastRightEnd = $beginRight + $cntLines;
+
+ if ($cntLines) {
+ $opCodes[] = ['equal', $beginLeft, $lastLeftEnd, $beginRight, $lastRightEnd];
+ }
+ }
+
+ return $opCodes;
+ }
+
+ /**
+ * Return a series of nested arrays containing different groups of generated
+ * opcodes for the differences between the strings with up to $context lines
+ * of surrounding content.
+ *
+ * Essentially what happens here is any big equal blocks of strings are stripped
+ * out, the smaller subsets of changes are then arranged in to their groups.
+ * This means that the sequence matcher and diffs do not need to include the full
+ * content of the different files but can still provide context as to where the
+ * changes are.
+ *
+ * @param array $opCodes
+ * @param int $context The number of lines of context to provide around the groups.
+ * @return array Nested array of all of the grouped opcodes.
+ */
+ public static function getGroupedOpcodes(array $opCodes, $context = 3)
+ {
+ if (empty($opCodes)) {
+ $opCodes = [
+ ['equal', 0, 1, 0, 1]
+ ];
+ }
+
+ if ($opCodes[0][0] === 'equal') {
+ $opCodes[0] = [
+ $opCodes[0][0],
+ max($opCodes[0][1], $opCodes[0][2] - $context),
+ $opCodes[0][2],
+ max($opCodes[0][3], $opCodes[0][4] - $context),
+ $opCodes[0][4]
+ ];
+ }
+
+ $lastItem = count($opCodes) - 1;
+ if ($opCodes[$lastItem][0] === 'equal') {
+ list($tag, $beginLeft, $endLeft, $beginRight, $endRight) = $opCodes[$lastItem];
+ $opCodes[$lastItem] = [
+ $tag,
+ $beginLeft,
+ min($endLeft, $beginLeft + $context),
+ $beginRight,
+ min($endRight, $beginRight + $context)
+ ];
+ }
+ /*
+ public $type;
+ public $beginLeft;
+ public $endLeft;
+ public $beginRight;
+ public $endRight;
+ */
+ $maxRange = $context * 2;
+ $groups = [];
+ $group = [];
+ foreach ($opCodes as list($tag, $beginLeft, $endLeft, $beginRight, $endRight)) {
+ if ($tag === 'equal' && $endLeft - $beginLeft > $maxRange) {
+ $group[] = [
+ $tag,
+ $beginLeft,
+ min($endLeft, $beginLeft + $context),
+ $beginRight,
+ min($endRight, $beginRight + $context)
+ ];
+ $groups[] = $group;
+ $group = [];
+ $beginLeft = max($beginLeft, $endLeft - $context);
+ $beginRight = max($beginRight, $endRight - $context);
+ }
+ $group[] = [$tag, $beginLeft, $endLeft, $beginRight, $endRight];
+ }
+
+ if (!empty($group) && !(count($group) === 1 && $group[0][0] === 'equal')) {
+ $groups[] = $group;
+ }
+
+ return $groups;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Ratio.php b/vendor/gipfl/diff/src/PhpDiff/Ratio.php
new file mode 100644
index 0000000..4db1460
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Ratio.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff;
+
+use function count;
+
+class Ratio
+{
+ /**
+ * @var SequenceMatcher
+ */
+ private $matcher;
+
+ /** @var float */
+ private $ratio;
+
+ /** @var array */
+ private $a;
+
+ /** @var array */
+ private $b;
+
+ /** @var array */
+ private $fullBCount;
+
+ public function __construct(SequenceMatcher $matcher)
+ {
+ $this->matcher = $matcher;
+ $this->a = $matcher->getLeftSequence();
+ $this->b = $matcher->getRightSequence();
+ }
+
+ /**
+ * Return a measure of the similarity between the two sequences.
+ * This will be a float value between 0 and 1.
+ *
+ * Out of all of the ratio calculation functions, this is the most
+ * expensive to call if getMatchingBlocks or getOpCodes is yet to be
+ * called. The other calculation methods (quickRatio and realquickRatio)
+ * can be used to perform quicker calculations but may be less accurate.
+ *
+ * The ratio is calculated as (2 * number of matches) / total number of
+ * elements in both sequences.
+ *
+ * @return float The calculated ratio.
+ */
+ public function getRatio()
+ {
+ if ($this->ratio === null) {
+ $matcher = $this->matcher;
+ $matches = array_reduce($matcher->getMatchingBlocks(), [$this, 'ratioReduce'], 0);
+ $this->ratio = $this->calculateRatio(
+ $matches,
+ count($this->a) + count($this->b)
+ );
+ }
+
+ return $this->ratio;
+ }
+
+ /**
+ * Helper function to calculate the number of matches for Ratio().
+ *
+ * @param int $sum The running total for the number of matches.
+ * @param array $triple Array containing the matching block triple to add to the running total.
+ * @return int The new running total for the number of matches.
+ */
+ private function ratioReduce($sum, $triple)
+ {
+ return $sum + ($triple[count($triple) - 1]);
+ }
+
+ /**
+ * Quickly return an upper bound ratio for the similarity of the strings.
+ * This is quicker to compute than Ratio().
+ *
+ * @return float The calculated ratio.
+ */
+ private function quickRatio()
+ {
+ $aLength = count($this->a);
+ $bLength = count($this->b);
+ if ($this->fullBCount === null) {
+ $this->fullBCount = [];
+ for ($i = 0; $i < $bLength; ++$i) {
+ $char = $this->b[$i];
+ $this->fullBCount[$char] = ArrayHelper::getPropertyOrDefault($this->fullBCount, $char, 0) + 1;
+ }
+ }
+
+ $avail = array();
+ $matches = 0;
+ for ($i = 0; $i < $aLength; ++$i) {
+ $char = $this->a[$i];
+ if (isset($avail[$char])) {
+ $numb = $avail[$char];
+ } else {
+ $numb = ArrayHelper::getPropertyOrDefault($this->fullBCount, $char, 0);
+ }
+ $avail[$char] = $numb - 1;
+ if ($numb > 0) {
+ ++$matches;
+ }
+ }
+
+ $this->calculateRatio($matches, $aLength + $bLength);
+ }
+
+ /**
+ * Return an upper bound ratio really quickly for the similarity of the strings.
+ * This is quicker to compute than Ratio() and quickRatio().
+ *
+ * @return float The calculated ratio.
+ */
+ private function realquickRatio()
+ {
+ $aLength = count($this->a);
+ $bLength = count($this->b);
+
+ return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength);
+ }
+
+ /**
+ * Helper function for calculating the ratio to measure similarity for the strings.
+ * The ratio is defined as being 2 * (number of matches / total length)
+ *
+ * @param int $matches The number of matches in the two strings.
+ * @param int $length The length of the two strings.
+ * @return float The calculated ratio.
+ */
+ private function calculateRatio($matches, $length = 0)
+ {
+ if ($length) {
+ return 2 * ($matches / $length);
+ }
+
+ return 1;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php
new file mode 100644
index 0000000..fc93d8d
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff\Renderer;
+
+use gipfl\Diff\PhpDiff;
+
+/**
+ * Abstract class for diff renderers in PHP DiffLib.
+ */
+abstract class AbstractRenderer
+{
+ /** @var PhpDiff */
+ public $diff;
+
+ /** @var array default options that apply to this renderer */
+ protected $defaultOptions = [];
+
+ /** @var array merged (user applied and default) options for the renderer */
+ protected $options = [];
+
+ /**
+ * @param PhpDiff $diff
+ * @param array $options Optionally, an array of the options for the renderer.
+ */
+ public function __construct(PhpDiff $diff, array $options = [])
+ {
+ $this->diff = $diff;
+ $this->setOptions($options);
+ }
+
+ /**
+ * Set the options of the renderer to those supplied in the passed in array.
+ * Options are merged with the default to ensure that there aren't any missing
+ * options.
+ *
+ * @param array $options
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+
+ abstract public function render();
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php
new file mode 100644
index 0000000..57f6cb4
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff\Renderer\Html;
+
+use gipfl\Diff\PhpDiff\Renderer\AbstractRenderer;
+
+/**
+ * Base renderer for rendering HTML based diffs for PHP DiffLib.
+ */
+class ArrayRenderer extends AbstractRenderer
+{
+ /** @var array default options */
+ protected $defaultOptions = [
+ 'tabSize' => 4
+ ];
+
+ /**
+ * Render and return an array structure suitable for generating HTML
+ * based differences. Generally called by subclasses that generate a
+ * HTML based diff and return an array of the changes to show in the diff.
+ *
+ * @return array An array of the generated chances, suitable for presentation in HTML.
+ */
+ public function render()
+ {
+ // As we'll be modifying a & b to include our change markers,
+ // we need to get the contents and store them here. That way
+ // we're not going to destroy the original data
+ $a = $this->diff->getLeft();
+ $b = $this->diff->getRight();
+
+ $changes = [];
+ foreach ($this->diff->getGroupedOpcodes() as $group) {
+ $changes[] = $this->renderOpcodesGroup($group, $a, $b);
+ }
+ return $changes;
+ }
+
+ protected function insertLineMarkers($line, $start, $end)
+ {
+ $last = $end + mb_strlen($line);
+
+ return mb_substr($line, 0, $start)
+ . "\0"
+ . mb_substr($line, $start, $last - $start)
+ . "\1"
+ . mb_substr($line, $last);
+ }
+
+ /**
+ * @param $group
+ * @param array $a
+ * @param array $b
+ * @return array
+ */
+ protected function renderOpcodesGroup($group, array $a, array $b)
+ {
+ $blocks = [];
+ $lastTag = null;
+ $lastBlock = 0;
+ foreach ($group as list($tag, $i1, $i2, $j1, $j2)) {
+ if ($tag === 'replace' && $i2 - $i1 === $j2 - $j1) {
+ for ($i = 0; $i < ($i2 - $i1); ++$i) {
+ $fromLine = $a[$i1 + $i];
+ $toLine = $b[$j1 + $i];
+
+ list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
+ if ($start !== 0 || $end !== 0) {
+ $a[$i1 + $i] = $this->insertLineMarkers($fromLine, $start, $end);
+ $b[$j1 + $i] = $this->insertLineMarkers($toLine, $start, $end);
+ }
+ }
+ }
+
+ if ($tag !== $lastTag) {
+ $blocks[] = [
+ 'tag' => $tag,
+ 'base' => [
+ 'offset' => $i1,
+ 'lines' => []
+ ],
+ 'changed' => [
+ 'offset' => $j1,
+ 'lines' => []
+ ]
+ ];
+ $lastBlock = count($blocks) - 1;
+ }
+
+ $lastTag = $tag;
+
+ if ($tag === 'equal') {
+ $lines = array_slice($a, $i1, ($i2 - $i1));
+ $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
+ $lines = array_slice($b, $j1, ($j2 - $j1));
+ $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
+ } else {
+ if ($tag === 'replace' || $tag === 'delete') {
+ $lines = array_slice($a, $i1, ($i2 - $i1));
+ $lines = $this->formatLines($lines);
+ $lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
+ $blocks[$lastBlock]['base']['lines'] += $lines;
+ }
+
+ if ($tag === 'replace' || $tag === 'insert') {
+ $lines = array_slice($b, $j1, ($j2 - $j1));
+ $lines = $this->formatLines($lines);
+ $lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
+ $blocks[$lastBlock]['changed']['lines'] += $lines;
+ }
+ }
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * Given two strings, determine where the changes in the two strings
+ * begin, and where the changes in the two strings end.
+ *
+ * @param string $fromLine The first string.
+ * @param string $toLine The second string.
+ * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
+ */
+ private function getChangeExtent($fromLine, $toLine)
+ {
+ $start = 0;
+ $limit = min(strlen($fromLine), strlen($toLine));
+ while ($start < $limit && $fromLine[$start] === $toLine[$start]) {
+ ++$start;
+ }
+ $end = -1;
+ $limit -= $start;
+ /** @noinspection SubStrUsedAsArrayAccessInspection $end is negative, array index needs PHP >= 7 */
+ while (-$end <= $limit && substr($fromLine, $end, 1) === substr($toLine, $end, 1)) {
+ --$end;
+ }
+ return [
+ $start,
+ $end + 1
+ ];
+ }
+
+ /**
+ * Format a series of lines suitable for output in a HTML rendered diff.
+ * This involves replacing tab characters with spaces, making the HTML safe
+ * for output, ensuring that double spaces are replaced with &nbsp; etc.
+ *
+ * @param array $lines lines to format.
+ * @return array formatted lines.
+ */
+ protected function formatLines($lines)
+ {
+ $lines = array_map([$this, 'ExpandTabs'], $lines);
+ $lines = array_map([$this, 'HtmlSafe'], $lines);
+ foreach ($lines as &$line) {
+ $line = preg_replace_callback('# ( +)|^ #', [$this, 'fixSpaces'], $line);
+ }
+ return $lines;
+ }
+
+ /**
+ * Replace a string containing spaces with a HTML representation using &nbsp;.
+ *
+ * @param string[] $matches preg matches.
+ * @return string HTML representation of the string.
+ */
+ private function fixSpaces(array $matches)
+ {
+ $count = 0;
+
+ if (count($matches) > 1) {
+ $spaces = $matches[1];
+ $count = strlen($spaces);
+ }
+
+ if ($count === 0) {
+ return '';
+ }
+
+ $div = floor($count / 2);
+ $mod = $count % 2;
+ return str_repeat('&nbsp; ', $div).str_repeat('&nbsp;', $mod);
+ }
+
+ /**
+ * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
+ *
+ * @param string $line The containing tabs to convert.
+ * @return string The line with the tabs converted to spaces.
+ */
+ private function expandTabs($line)
+ {
+ return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
+ }
+
+ /**
+ * Make a string containing HTML safe for output on a page.
+ *
+ * @param string $string The string.
+ * @return string The string with the HTML characters replaced by entities.
+ */
+ private function htmlSafe($string)
+ {
+ return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php
new file mode 100644
index 0000000..6587de1
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff\Renderer\Html;
+
+/**
+ * Inline HTML diff generator for PHP DiffLib.
+ */
+class Inline extends ArrayRenderer
+{
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed inline (under each other)
+ *
+ * @return string The generated inline diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+ $html = '';
+ if (empty($changes)) {
+ return $html;
+ }
+
+ $html .= '<table class="Differences DifferencesInline">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>Old</th>';
+ $html .= '<th>New</th>';
+ $html .= '<th>Differences</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ foreach ($changes as $i => $blocks) {
+ // If this is a separate block, we're condensing code so output ...,
+ // indicating a significant portion of the code has been collapsed as
+ // it is the same
+ if ($i > 0) {
+ $html .= '<tbody class="Skipped">';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<td>&nbsp;</td>';
+ $html .= '</tbody>';
+ }
+
+ foreach ($blocks as $change) {
+ $html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
+ // Equal changes should be shown on both sides of the diff
+ if ($change['tag'] === 'equal') {
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>' . $fromLine . '</th>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Left">' . $line . '</td>';
+ $html .= '</tr>';
+ }
+ } elseif ($change['tag'] === 'insert') {
+ // Added lines only on the right side
+ foreach ($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><ins>' . $line . '</ins>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ } elseif ($change['tag'] === 'delete') {
+ // Show deleted lines only on the left side
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>' . $fromLine . '</th>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left"><del>' . $line . '</del>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ } elseif ($change['tag'] === 'replace') {
+ // Show modified lines on both sides
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>' . $fromLine . '</th>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left"><span>' . $line . '</span></td>';
+ $html .= '</tr>';
+ }
+
+ foreach ($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><span>' . $line . '</span></td>';
+ $html .= '</tr>';
+ }
+ }
+ $html .= '</tbody>';
+ }
+ }
+ $html .= '</table>';
+
+ return $html;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php
new file mode 100644
index 0000000..2d16e08
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff\Renderer\Html;
+
+/**
+ * Side by Side HTML diff generator for PHP DiffLib.
+ */
+class SideBySide extends ArrayRenderer
+{
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed side by side.
+ *
+ * @return string The generated side by side diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+
+ $html = '';
+ if (empty($changes)) {
+ return $html;
+ }
+
+ $html .= '<table class="Differences DifferencesSideBySide">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th colspan="2">Old Version</th>';
+ $html .= '<th colspan="2">New Version</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ foreach ($changes as $i => $blocks) {
+ if ($i > 0) {
+ $html .= '<tbody class="Skipped">';
+ $html .= '<th>&hellip;</th><td>&nbsp;</td>';
+ $html .= '<th>&hellip;</th><td>&nbsp;</td>';
+ $html .= '</tbody>';
+ }
+
+ foreach ($blocks as $change) {
+ $html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
+ // Equal changes should be shown on both sides of the diff
+ if ($change['tag'] === 'equal') {
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>' . $fromLine . '</th>';
+ $html .= '<td class="Left"><span>' . $line . '</span>&nbsp;</td>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><span>' . $line . '</span>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ } elseif ($change['tag'] === 'insert') {
+ // Added lines only on the right side
+ foreach ($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left">&nbsp;</td>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><ins>' . $line . '</ins>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ } elseif ($change['tag'] === 'delete') {
+ // Show deleted lines only on the left side
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>' . $fromLine . '</th>';
+ $html .= '<td class="Left"><del>' . $line . '</del>&nbsp;</td>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Right">&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ } elseif ($change['tag'] === 'replace') {
+ // Show modified lines on both sides
+ if (count($change['base']['lines']) >= count($change['changed']['lines'])) {
+ foreach ($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>' . $line . '</span>&nbsp;</td>';
+ if (!isset($change['changed']['lines'][$no])) {
+ $toLine = '&nbsp;';
+ $changedLine = '&nbsp;';
+ } else {
+ $toLine = $change['base']['offset'] + $no + 1;
+ $changedLine = '<span>'.$change['changed']['lines'][$no].'</span>';
+ }
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right">' . $changedLine . '</td>';
+ $html .= '</tr>';
+ }
+ } else {
+ foreach ($change['changed']['lines'] as $no => $changedLine) {
+ if (!isset($change['base']['lines'][$no])) {
+ $fromLine = '&nbsp;';
+ $line = '&nbsp;';
+ } else {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $line = '<span>' . $change['base']['lines'][$no] . '</span>';
+ }
+ $html .= '<tr>';
+ $html .= '<th>' . $fromLine . '</th>';
+ $html .= '<td class="Left"><span>' . $line . '</span>&nbsp;</td>';
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right">' . $changedLine . '</td>';
+ $html .= '</tr>';
+ }
+ }
+ }
+ $html .= '</tbody>';
+ }
+ }
+ $html .= '</table>';
+
+ return $html;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php
new file mode 100644
index 0000000..b8d9cad
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff\Renderer\Text;
+
+use gipfl\Diff\PhpDiff\Renderer\AbstractRenderer;
+
+/**
+ * Context diff generator for PHP DiffLib.
+ */
+class Context extends AbstractRenderer
+{
+ /**
+ * @var array Array of the different opcode tags and how they map to the context diff equivalent.
+ */
+ private $tagMap = [
+ 'insert' => '+',
+ 'delete' => '-',
+ 'replace' => '!',
+ 'equal' => ' '
+ ];
+
+ /**
+ * Render and return a context formatted (old school!) diff file.
+ *
+ * @return string The generated context diff.
+ */
+ public function render()
+ {
+ $diff = '';
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach ($opCodes as $group) {
+ $diff .= "***************\n";
+ $lastItem = count($group)-1;
+ $i1 = $group[0][1];
+ $i2 = $group[$lastItem][2];
+ $j1 = $group[0][3];
+ $j2 = $group[$lastItem][4];
+
+ if ($i2 - $i1 >= 2) {
+ $diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n";
+ } else {
+ $diff .= '*** '.$i2." ****\n";
+ }
+
+ if ($j2 - $j1 >= 2) {
+ $separator = '--- '.($j1 + 1).','.$j2." ----\n";
+ } else {
+ $separator = '--- '.$j2." ----\n";
+ }
+
+ $hasVisible = false;
+ foreach ($group as $code) {
+ if ($code[0] === 'replace' || $code[0] === 'delete') {
+ $hasVisible = true;
+ break;
+ }
+ }
+
+ if ($hasVisible) {
+ foreach ($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if ($tag === 'insert') {
+ continue;
+ }
+ $diff .= $this->tagMap[$tag]
+ . ' '
+ . implode("\n" . $this->tagMap[$tag] . ' ', $this->diff->getLeft($i1, $i2))
+ . "\n";
+ }
+ }
+
+ $hasVisible = false;
+ foreach ($group as $code) {
+ if ($code[0] === 'replace' || $code[0] === 'insert') {
+ $hasVisible = true;
+ break;
+ }
+ }
+
+ $diff .= $separator;
+
+ if ($hasVisible) {
+ foreach ($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if ($tag === 'delete') {
+ continue;
+ }
+ $diff .= $this->tagMap[$tag]
+ . ' '
+ . implode("\n" . $this->tagMap[$tag] . ' ', $this->diff->getRight($j1, $j2))
+ . "\n";
+ }
+ }
+ }
+
+ return $diff;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php
new file mode 100644
index 0000000..afeb96d
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff\Renderer\Text;
+
+use gipfl\Diff\PhpDiff\Renderer\AbstractRenderer;
+
+/**
+ * Unified diff generator for PHP DiffLib.
+ */
+class Unified extends AbstractRenderer
+{
+ /**
+ * Render and return a unified diff.
+ *
+ * @return string The unified diff.
+ */
+ public function render()
+ {
+ $diff = '';
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach ($opCodes as $group) {
+ $lastItem = count($group)-1;
+ $i1 = $group[0][1];
+ $i2 = $group[$lastItem][2];
+ $j1 = $group[0][3];
+ $j2 = $group[$lastItem][4];
+
+ if ($i1 === 0 && $i2 === 0) {
+ $i1 = -1;
+ $i2 = -1;
+ }
+
+ $diff .= '@@ -' . ($i1 + 1) . ',' . ($i2 - $i1) . ' +' . ($j1 + 1) . ',' . ($j2 - $j1) . " @@\n";
+ foreach ($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if ($tag === 'equal') {
+ $diff .= ' ' . implode("\n ", $this->diff->getLeft($i1, $i2))."\n";
+ } else {
+ if ($tag === 'replace' || $tag === 'delete') {
+ $diff .= '-' . implode("\n-", $this->diff->getLeft($i1, $i2))."\n";
+ }
+
+ if ($tag === 'replace' || $tag === 'insert') {
+ $diff .= '+' . implode("\n+", $this->diff->getRight($j1, $j2))."\n";
+ }
+ }
+ }
+ }
+
+ return $diff;
+ }
+}
diff --git a/vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php b/vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php
new file mode 100644
index 0000000..a185762
--- /dev/null
+++ b/vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php
@@ -0,0 +1,438 @@
+<?php
+
+namespace gipfl\Diff\PhpDiff;
+
+use function array_merge;
+use function count;
+use function is_array;
+use function str_replace;
+use function str_split;
+use function strtolower;
+
+/**
+ * Sequence matcher for Diff
+ */
+class SequenceMatcher
+{
+ /**
+ * Either a string or an array containing a callback function to determine
+ * if a line is "junk" or not
+ *
+ * @var string|array
+ */
+ private $junkCallback;
+
+ /**
+ * @var array The first sequence to compare against.
+ */
+ private $left = [];
+
+ /**
+ * @var array The second sequence.
+ */
+ private $right = [];
+
+ /**
+ * @var array Characters that are considered junk from the second sequence. Characters are the array key.
+ */
+ private $junkCharacters = [];
+
+ /**
+ * @var array Array of indices that do not contain junk elements.
+ */
+ private $b2j = [];
+
+ private $options = [];
+
+ private $defaultOptions = [
+ 'ignoreNewLines' => false,
+ 'ignoreWhitespace' => false,
+ 'ignoreCase' => false
+ ];
+
+ /** @var array|null */
+ private $matchingBlocks;
+
+ /** @var array|null */
+ private $opCodes;
+
+ /**
+ * The constructor. With the sequences being passed, they'll be set for the
+ * sequence matcher and it will perform a basic cleanup & calculate junk
+ * elements.
+ *
+ * @param string|array $left A string or array containing the lines to compare against.
+ * @param string|array $right A string or array containing the lines to compare.
+ * @param string|array $junkCallback Either an array or string that references a callback
+ * function (if there is one) to determine 'junk' characters.
+ * @param array $options
+ */
+ public function __construct($left, $right, $junkCallback = null, $options = [])
+ {
+ $this->junkCallback = $junkCallback;
+ $this->setOptions($options);
+ $this->setSequences($left, $right);
+ }
+
+ public function setOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+
+ /**
+ * Set the first and second sequences to use with the sequence matcher.
+ *
+ * @param string|array $left A string or array containing the lines to compare against.
+ * @param string|array $right A string or array containing the lines to compare.
+ */
+ public function setSequences($left, $right)
+ {
+ $this->setLeftSequence($left);
+ $this->setRightSequence($right);
+ }
+
+ /**
+ * Set the first sequence and reset any internal caches to indicate that
+ * when calling the calculation methods, we need to recalculate them.
+ *
+ * @param string|array $sequence The sequence to set as the first sequence.
+ */
+ protected function setLeftSequence($sequence)
+ {
+ if (!is_array($sequence)) {
+ $sequence = str_split($sequence);
+ }
+ if ($sequence === $this->left) {
+ return;
+ }
+
+ $this->resetCalculation();
+ $this->left = $sequence;
+ }
+
+ /**
+ * Set the second sequence ($b) and reset any internal caches to indicate that
+ * when calling the calculation methods, we need to recalculate them.
+ *
+ * @param string|array $sequence The sequence to set as the second sequence.
+ */
+ protected function setRightSequence($sequence)
+ {
+ if (!is_array($sequence)) {
+ $sequence = str_split($sequence);
+ }
+ if ($sequence === $this->right) {
+ return;
+ }
+
+ $this->resetCalculation();
+ $this->right = $sequence;
+ $this->generateRightChain();
+ }
+
+ protected function resetCalculation()
+ {
+ $this->matchingBlocks = null;
+ $this->opCodes = null;
+ }
+
+ /**
+ * @return array
+ */
+ public function getLeftSequence()
+ {
+ return $this->left;
+ }
+
+ /**
+ * @return array
+ */
+ public function getRightSequence()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Generate the internal arrays containing the list of junk and non-junk
+ * characters for the second ($b) sequence.
+ */
+ private function generateRightChain()
+ {
+ $length = count($this->right);
+ $this->b2j = [];
+ $popularDict = [];
+
+ foreach ($this->right as $i => $char) {
+ if (isset($this->b2j[$char])) {
+ if ($length >= 200 && count($this->b2j[$char]) * 100 > $length) {
+ $popularDict[$char] = 1;
+ unset($this->b2j[$char]);
+ } else {
+ $this->b2j[$char][] = $i;
+ }
+ } else {
+ $this->b2j[$char] = [$i];
+ }
+ }
+
+ // Remove leftovers
+ foreach (array_keys($popularDict) as $char) {
+ unset($this->b2j[$char]);
+ }
+
+ $this->junkCharacters = [];
+ if (is_callable($this->junkCallback)) {
+ foreach (array_keys($popularDict) as $char) {
+ if (call_user_func($this->junkCallback, $char)) {
+ $this->junkCharacters[$char] = 1;
+ unset($popularDict[$char]);
+ }
+ }
+
+ foreach (array_keys($this->b2j) as $char) {
+ if (call_user_func($this->junkCallback, $char)) {
+ $this->junkCharacters[$char] = 1;
+ unset($this->b2j[$char]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if a particular character is in the junk dictionary
+ * for the list of junk characters.
+ *
+ * @param $b
+ * @return boolean whether the character is considered junk
+ */
+ private function isBJunk($b)
+ {
+ if (isset($this->junkCharacters[$b])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Find the longest matching block in the two sequences, as defined by the
+ * lower and upper constraints for each sequence. (for the first sequence,
+ * $alo - $ahi and for the second sequence, $blo - $bhi)
+ *
+ * Essentially, of all of the maximal matching blocks, return the one that
+ * starts earliest in $a, and all of those maximal matching blocks that
+ * start earliest in $a, return the one that starts earliest in $b.
+ *
+ * If the junk callback is defined, do the above but with the restriction
+ * that the junk element appears in the block. Extend it as far as possible
+ * by matching only junk elements in both $a and $b.
+ *
+ * @param int $beginLeft The lower constraint for the first sequence.
+ * @param int $endLeft The upper constraint for the first sequence.
+ * @param int $beginRight The lower constraint for the second sequence.
+ * @param int $endRight The upper constraint for the second sequence.
+ * @return array Array containing the longest match that includes the starting
+ * position in $a, start in $b and the length/size.
+ */
+ public function findLongestMatch($beginLeft, $endLeft, $beginRight, $endRight)
+ {
+ $left = $this->left;
+ $right = $this->right;
+
+ $bestBeginLeft = $beginLeft;
+ $bestBeginRight = $beginRight;
+ $bestSize = 0;
+
+ $j2Len = [];
+ $nothing = [];
+
+ for ($currentLeft = $beginLeft; $currentLeft < $endLeft; ++$currentLeft) {
+ $newJ2Len = [];
+ $junkList = ArrayHelper::getPropertyOrDefault($this->b2j, $left[$currentLeft], $nothing);
+ foreach ($junkList as $junk) {
+ if ($junk < $beginRight) {
+ continue;
+ }
+ if ($junk >= $endRight) {
+ break;
+ }
+
+ $k = ArrayHelper::getPropertyOrDefault($j2Len, $junk -1, 0) + 1;
+ $newJ2Len[$junk] = $k;
+ if ($k > $bestSize) {
+ $bestBeginLeft = $currentLeft - $k + 1;
+ $bestBeginRight = $junk - $k + 1;
+ $bestSize = $k;
+ }
+ }
+
+ $j2Len = $newJ2Len;
+ }
+
+ while ($bestBeginLeft > $beginLeft
+ && $bestBeginRight > $beginRight
+ && !$this->isBJunk($right[$bestBeginRight - 1])
+ && !$this->linesAreDifferent($bestBeginLeft - 1, $bestBeginRight - 1)
+ ) {
+ --$bestBeginLeft;
+ --$bestBeginRight;
+ ++$bestSize;
+ }
+
+ while ($bestBeginLeft + $bestSize < $endLeft && ($bestBeginRight + $bestSize) < $endRight
+ && !$this->isBJunk($right[$bestBeginRight + $bestSize])
+ && !$this->linesAreDifferent($bestBeginLeft + $bestSize, $bestBeginRight + $bestSize)
+ ) {
+ ++$bestSize;
+ }
+
+ while ($bestBeginLeft > $beginLeft
+ && $bestBeginRight > $beginRight
+ && $this->isBJunk($right[$bestBeginRight - 1])
+ && !$this->linesAreDifferent($bestBeginLeft - 1, $bestBeginRight - 1)
+ ) {
+ --$bestBeginLeft;
+ --$bestBeginRight;
+ ++$bestSize;
+ }
+
+ while ($bestBeginLeft + $bestSize < $endLeft
+ && $bestBeginRight + $bestSize < $endRight
+ && $this->isBJunk($right[$bestBeginRight + $bestSize])
+ && !$this->linesAreDifferent($bestBeginLeft + $bestSize, $bestBeginRight + $bestSize)
+ ) {
+ ++$bestSize;
+ }
+
+ return [$bestBeginLeft, $bestBeginRight, $bestSize];
+ }
+
+ /**
+ * Check if the two lines at the given indexes are different or not.
+ *
+ * @param int $leftIndex Line number to check against in a.
+ * @param int $rightIndex Line number to check against in b.
+ * @return boolean True if the lines are different and false if not.
+ */
+ public function linesAreDifferent($leftIndex, $rightIndex)
+ {
+ $leftLine = $this->left[$leftIndex];
+ $rightLine = $this->right[$rightIndex];
+
+ if ($this->options['ignoreWhitespace']) {
+ $replace = ["\t", ' '];
+ $leftLine = str_replace($replace, '', $leftLine);
+ $rightLine = str_replace($replace, '', $rightLine);
+ }
+
+ if ($this->options['ignoreCase']) {
+ $leftLine = strtolower($leftLine);
+ $rightLine = strtolower($rightLine);
+ }
+
+ return $leftLine !== $rightLine;
+ }
+
+ /**
+ * Return a nested set of arrays for all of the matching sub-sequences
+ * in the strings $a and $b.
+ *
+ * Each block contains the lower constraint of the block in $a, the lower
+ * constraint of the block in $b and finally the number of lines that the
+ * block continues for.
+ *
+ * @return array Nested array of the matching blocks, as described by the function.
+ */
+ public function getMatchingBlocks()
+ {
+ if ($this->matchingBlocks === null) {
+ $this->matchingBlocks = $this->calculateMatchingBlocks();
+ }
+
+ return $this->matchingBlocks;
+ }
+
+ public function calculateMatchingBlocks()
+ {
+ $leftLength = count($this->left);
+ $rightLength = count($this->right);
+
+ $queue = [
+ [0, $leftLength, 0, $rightLength]
+ ];
+
+ $matchingBlocks = [];
+ while (!empty($queue)) {
+ list($leftBegin, $leftEnd, $rightBegin, $rightEnd) = array_pop($queue);
+ $block = $this->findLongestMatch($leftBegin, $leftEnd, $rightBegin, $rightEnd);
+ list($bestBeginLeft, $bestBeginRight, $bestSize) = $block;
+ if ($bestSize) {
+ $matchingBlocks[] = $block;
+ if ($leftBegin < $bestBeginLeft && $rightBegin < $bestBeginRight) {
+ $queue[] = [
+ $leftBegin,
+ $bestBeginLeft,
+ $rightBegin,
+ $bestBeginRight
+ ];
+ }
+
+ if ($bestBeginLeft + $bestSize < $leftEnd && $bestBeginRight + $bestSize < $rightEnd) {
+ $queue[] = [
+ $bestBeginLeft + $bestSize,
+ $leftEnd,
+ $bestBeginRight + $bestSize,
+ $rightEnd
+ ];
+ }
+ }
+ }
+
+ usort($matchingBlocks, [ArrayHelper::class, 'tupleSort']);
+
+ return static::getNonAdjacentBlocks($matchingBlocks, $leftLength, $rightLength);
+ }
+
+ public function getOpcodes()
+ {
+ if ($this->opCodes === null) {
+ $this->opCodes = OpCodeHelper::calculateOpCodes($this->getMatchingBlocks());
+ }
+
+ return $this->opCodes;
+ }
+
+ /**
+ * @param array $matchingBlocks
+ * @param $leftLength
+ * @param $rightLength
+ * @return array
+ */
+ protected static function getNonAdjacentBlocks(array $matchingBlocks, $leftLength, $rightLength)
+ {
+ $newLeft = 0;
+ $newRight = 0;
+ $newCnt = 0;
+ $nonAdjacent = [];
+ foreach ($matchingBlocks as list($beginLeft, $beginRight, $cntLines)) {
+ if ($newLeft + $newCnt === $beginLeft && $newRight + $newCnt === $beginRight) {
+ $newCnt += $cntLines;
+ } else {
+ if ($newCnt) {
+ $nonAdjacent[] = [$newLeft, $newRight, $newCnt];
+ }
+
+ $newLeft = $beginLeft;
+ $newRight = $beginRight;
+ $newCnt = $cntLines;
+ }
+ }
+
+ if ($newCnt) {
+ $nonAdjacent[] = [$newLeft, $newRight, $newCnt];
+ }
+
+ $nonAdjacent[] = [$leftLength, $rightLength, 0];
+ return $nonAdjacent;
+ }
+}
diff --git a/vendor/gipfl/format/composer.json b/vendor/gipfl/format/composer.json
new file mode 100644
index 0000000..10c3b90
--- /dev/null
+++ b/vendor/gipfl/format/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "gipfl/format",
+ "description": "Arbitrary collection of Format helpers",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Format\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ext-intl": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^7.5 || ^6.5 || ^5.7",
+ "squizlabs/php_codesniffer": "^3.6"
+ }
+}
diff --git a/vendor/gipfl/format/src/LocalDateFormat.php b/vendor/gipfl/format/src/LocalDateFormat.php
new file mode 100644
index 0000000..60a29e4
--- /dev/null
+++ b/vendor/gipfl/format/src/LocalDateFormat.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace gipfl\Format;
+
+use IntlDateFormatter;
+
+class LocalDateFormat
+{
+ use LocaleAwareness;
+
+ /** @var IntlDateFormatter */
+ protected $formatter;
+
+ /**
+ * @param $time
+ * @return string
+ */
+ public function getFullDay($time)
+ {
+ return $this->formatter()->format($time);
+ }
+
+ protected function formatter()
+ {
+ if ($this->formatter === null) {
+ $this->formatter = new IntlDateFormatter(
+ $this->getLocale(),
+ IntlDateFormatter::FULL,
+ IntlDateFormatter::NONE
+ );
+ $this->formatter->setTimeZone($this->getTimezone());
+ }
+
+ return $this->formatter;
+ }
+
+ protected function reset()
+ {
+ $this->formatter = null;
+ }
+}
diff --git a/vendor/gipfl/format/src/LocalTimeFormat.php b/vendor/gipfl/format/src/LocalTimeFormat.php
new file mode 100644
index 0000000..1ea3ce7
--- /dev/null
+++ b/vendor/gipfl/format/src/LocalTimeFormat.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace gipfl\Format;
+
+use IntlDateFormatter;
+use RuntimeException;
+
+class LocalTimeFormat
+{
+ use LocaleAwareness;
+
+ /** @var IntlDateFormatter */
+ protected $formatter;
+
+ /**
+ * For available symbols please see:
+ * https://unicode-org.github.io/icu/userguide/format_parse/datetime/#date-field-symbol-table
+ *
+ * @param int|float $time Hint: also supports DateTime, DateTimeInterface since 7.1.5
+ * @return string
+ */
+ public function format($time, $pattern)
+ {
+ $result = $this->formatter($pattern)->format($time);
+ if ($result === false) {
+ throw new RuntimeException(sprintf(
+ 'Failed to format %s as "%s": %s (%d)',
+ $time,
+ $pattern,
+ $this->formatter->getErrorMessage(),
+ $this->formatter->getErrorCode()
+ ));
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param $time
+ * @return string
+ */
+ public function getWeekOfYear($time)
+ {
+ return $this->format($time, 'ww');
+ }
+
+ /**
+ * @param $time
+ * @return int
+ */
+ public function getNumericWeekOfYear($time)
+ {
+ return (int) $this->format($time, 'w');
+ }
+
+ /**
+ * @param $time
+ * @return string
+ */
+ public function getDayInMonth($time)
+ {
+ return $this->format($time, 'dd');
+ }
+
+ /**
+ * @param $time
+ * @return int
+ */
+ public function getNumericDayInMonth($time)
+ {
+ return (int) $this->format($time, 'd');
+ }
+
+ /**
+ * @param $time
+ * @return string
+ */
+ public function getWeekdayName($time)
+ {
+ return $this->format($time, 'cccc');
+ }
+
+ /**
+ * @param $time
+ * @return string
+ */
+ public function getShortWeekdayName($time)
+ {
+ return $this->format($time, 'ccc');
+ }
+
+ /**
+ * e.g. September
+ *
+ * @param $time
+ * @return string
+ */
+ public function getMonthName($time)
+ {
+ return $this->format($time, 'LLLL');
+ }
+
+ /**
+ * e.g. Sep
+ *
+ * @param $time
+ * @return string
+ */
+ public function getShortMonthName($time)
+ {
+ return $this->format($time, 'LLL');
+ }
+
+ /**
+ * e.g. 2021
+ * @param $time
+ * @return string
+ */
+ public function getYear($time)
+ {
+ return $this->format($time, 'y');
+ }
+
+ /**
+ * e.g. 21
+ *
+ * @param $time
+ * @return string
+ */
+ public function getShortYear($time)
+ {
+ return $this->format($time, 'yy');
+ }
+
+ /**
+ * e.g. 21:50:12
+ *
+ * @param $time
+ * @return string
+ */
+ public function getTime($time)
+ {
+ if ($this->wantsAmPm()) {
+ return $this->format($time, 'h:mm:ss a');
+ }
+
+ return $this->format($time, 'H:mm:ss');
+ }
+
+ /**
+ * e.g. 21:50
+ *
+ * @param $time
+ * @return string
+ */
+ public function getShortTime($time)
+ {
+ if ($this->wantsAmPm()) {
+ return $this->format($time, 'K:mm a');
+ }
+
+ return $this->format($time, 'H:mm');
+ }
+
+ protected function formatter($pattern)
+ {
+ if ($this->formatter === null) {
+ $this->formatter = new IntlDateFormatter(
+ $this->getLocale(),
+ IntlDateFormatter::GREGORIAN,
+ IntlDateFormatter::GREGORIAN
+ );
+ $this->formatter->setTimeZone($this->getTimezone());
+ }
+ $this->formatter->setPattern($pattern);
+
+ return $this->formatter;
+ }
+
+ protected function reset()
+ {
+ $this->formatter = null;
+ }
+}
diff --git a/vendor/gipfl/format/src/LocaleAwareness.php b/vendor/gipfl/format/src/LocaleAwareness.php
new file mode 100644
index 0000000..cb1a417
--- /dev/null
+++ b/vendor/gipfl/format/src/LocaleAwareness.php
@@ -0,0 +1,94 @@
+<?php
+
+namespace gipfl\Format;
+
+use DateTimeZone;
+use IntlTimeZone;
+use RuntimeException;
+
+trait LocaleAwareness
+{
+ /** @var string */
+ protected $locale;
+
+ /** @var DateTimeZone|IntlTimeZone */
+ protected $timezone;
+
+ /**
+ * @param string $locale
+ * @return void
+ */
+ public function setLocale($locale)
+ {
+ if ($this->locale !== $locale) {
+ $this->locale = (string) $locale;
+ $this->reset();
+ }
+ }
+
+ /**
+ * @param DateTimeZone|IntlTimeZone $timezone
+ * @return void
+ */
+ public function setTimezone($timezone)
+ {
+ // Hint: type checking is delegated to timeZonesAreEqual
+ if (self::timeZonesAreEqual($this->timezone, $timezone)) {
+ return;
+ }
+
+ $this->timezone = $timezone;
+ $this->reset();
+ }
+
+ protected function wantsAmPm()
+ {
+ // TODO: complete this list
+ return in_array($this->getLocale(), ['en_US', 'en_US.UTF-8']);
+ }
+
+ protected function isUsEnglish()
+ {
+ return in_array($this->getLocale(), ['en_US', 'en_US.UTF-8']);
+ }
+
+ protected function getLocale()
+ {
+ if ($this->locale === null) {
+ $this->locale = setlocale(LC_TIME, 0) ?: 'C';
+ }
+
+ return $this->locale;
+ }
+
+ protected function getTimezone()
+ {
+ if ($this->timezone === null) {
+ $this->timezone = new DateTimeZone(date_default_timezone_get());
+ }
+
+ return $this->timezone;
+ }
+
+ protected static function timeZonesAreEqual($left, $right)
+ {
+ if ($left === null && $right !== null) {
+ return false;
+ }
+ if ($left !== null && $right === null) {
+ return false;
+ }
+ if ($left instanceof DateTimeZone) {
+ return $right instanceof DateTimeZone && $left->getName() === $right->getName();
+ }
+ if ($left instanceof IntlTimeZone) {
+ return $right instanceof IntlTimeZone && $left->getID() === $right->getID();
+ }
+
+ throw new RuntimeException(sprintf(
+ 'Valid timezone expected, got left=%s, right=%s',
+ is_object($left) ? get_class($left) : gettype($left),
+ is_object($right) ? get_class($right) : gettype($right)
+ ));
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/composer.json b/vendor/gipfl/icinga-cli-daemon/composer.json
new file mode 100644
index 0000000..ec38ba2
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "gipfl/icinga-cli-daemon",
+ "type": "library",
+ "description": "Helpers for Icinga CLI Daemons",
+ "homepage": "https://github.com/gipfl/icinga-cli-daemon",
+ "config": {
+ "sort-packages": true,
+ "platform": {
+ "php": "5.6.3"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\IcingaCliDaemon\\": "src/"
+ }
+ },
+ "require": {
+ "php": ">=5.6.3",
+ "ext-posix": "*",
+ "gipfl/cli": ">=0.5.0",
+ "react/event-loop": ">=1.1",
+ "react/promise": ">=2.7"
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php b/vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php
new file mode 100644
index 0000000..08b188f
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/DbResourceConfigWatch.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+use Icinga\Application\Config;
+use InvalidArgumentException;
+use React\EventLoop\LoopInterface;
+
+/**
+ * DbResourceConfigWatch
+ *
+ * Checks every $interval = 3 seconds for changed DB resource configuration.
+ * Notifies registered callbacksin case this happens.
+ */
+class DbResourceConfigWatch
+{
+ /** @var string */
+ protected $configFile;
+
+ /** @var string */
+ protected $resourceConfigFile;
+
+ /** @var string|null */
+ protected $dbResourceName;
+
+ /** @var array|null|false It's false on initialization to trigger */
+ protected $resourceConfig = false;
+
+ /** @var int|float */
+ protected $interval = 3;
+
+ /** @var callable[] */
+ protected $callbacks = [];
+
+ /**
+ * @param string $dbResourceName
+ * @return DbResourceConfigWatch
+ */
+ public static function name($dbResourceName)
+ {
+ $self = new static();
+ $self->dbResourceName = $dbResourceName;
+
+ return $self;
+ }
+
+ /**
+ * @param string $moduleName
+ * @return DbResourceConfigWatch
+ */
+ public static function module($moduleName)
+ {
+ $self = new static();
+ $self->configFile = Config::module($moduleName)->getConfigFile();
+ $self->resourceConfigFile = Config::app('resources')->getConfigFile();
+
+ return $self;
+ }
+
+ /**
+ * @param int|float $interval
+ * @return $this
+ */
+ public function setInterval($interval)
+ {
+ if (! \is_int($interval) && ! \is_float($interval)) {
+ throw new InvalidArgumentException(
+ '$interval needs to be either int or float'
+ );
+ }
+ $this->interval = $interval;
+
+ return $this;
+ }
+
+ /**
+ * @param callable $callable
+ * @return $this
+ */
+ public function notify($callable)
+ {
+ if (! \is_callable($callable)) {
+ throw new InvalidArgumentException('$callable needs to be callable');
+ }
+ $this->callbacks[] = $callable;
+
+ return $this;
+ }
+
+ /**
+ * @param LoopInterface $loop
+ */
+ public function run(LoopInterface $loop)
+ {
+ $check = function () {
+ $this->checkForFreshConfig();
+ };
+ $loop->addPeriodicTimer($this->interval, $check);
+ $loop->futureTick($check);
+ }
+
+ protected function checkForFreshConfig()
+ {
+ if ($this->configHasBeenChanged()) {
+ $this->emitNewConfig($this->resourceConfig);
+ }
+ }
+
+ protected function emitNewConfig($config)
+ {
+ foreach ($this->callbacks as $callback) {
+ $callback($config);
+ }
+ }
+
+ protected function getResourceName()
+ {
+ if ($this->dbResourceName) {
+ return $this->dbResourceName;
+ } else {
+ return $this->loadDbResourceName();
+ }
+ }
+
+ protected function loadDbResourceName()
+ {
+ $parsed = @\parse_ini_file($this->configFile, true);
+ if (isset($parsed['db']['resource'])) {
+ return $parsed['db']['resource'];
+ } else {
+ return null;
+ }
+ }
+
+ protected function loadDbConfigFromDisk($name)
+ {
+ if ($name === null) {
+ return null;
+ }
+
+ $parsed = @\parse_ini_file($this->resourceConfigFile, true);
+ if (isset($parsed[$name])) {
+ $section = $parsed[$name];
+ \ksort($section);
+
+ return $section;
+ } else {
+ return null;
+ }
+ }
+
+ protected function configHasBeenChanged()
+ {
+ $resource = $this->loadDbConfigFromDisk($this->loadDbResourceName());
+ if ($resource !== $this->resourceConfig) {
+ $this->resourceConfig = $resource;
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php b/vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php
new file mode 100644
index 0000000..9ca5e5f
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/FinishedProcessState.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+class FinishedProcessState
+{
+ /** @var int|null */
+ protected $exitCode;
+
+ /** @var int|null */
+ protected $termSignal;
+
+ public function __construct($exitCode, $termSignal)
+ {
+ $this->exitCode = $exitCode;
+ $this->termSignal = $termSignal;
+ }
+
+ public function succeeded()
+ {
+ return $this->exitCode === 0;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getExitCode()
+ {
+ return $this->exitCode;
+ }
+
+ public function getTermSignal()
+ {
+ return $this->termSignal;
+ }
+
+ public function getCombinedExitCode()
+ {
+ if ($this->exitCode === null) {
+ if ($this->termSignal === null) {
+ return 255;
+ } else {
+ return 255 + $this->termSignal;
+ }
+ } else {
+ return $this->exitCode;
+ }
+ }
+
+ public function getReason()
+ {
+ if ($this->exitCode === null) {
+ if ($this->termSignal === null) {
+ return 'Process died';
+ } else {
+ return 'Process got terminated with SIGNAL ' . $this->termSignal;
+ }
+ } else {
+ if ($this->exitCode === 0) {
+ return 'Process finished successfully';
+ } else {
+ return 'Process exited with exit code ' . $this->exitCode;
+ }
+ }
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php b/vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php
new file mode 100644
index 0000000..8aad4c6
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/IcingaCli.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+use Exception;
+use Evenement\EventEmitterTrait;
+use gipfl\Protocol\JsonRpc\Connection;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Promise\Stream;
+
+class IcingaCli
+{
+ use EventEmitterTrait;
+
+ /** @var IcingaCliRunner */
+ protected $runner;
+
+ /** @var Connection|null */
+ protected $rpc;
+
+ protected $arguments = [];
+
+ /** @var \React\Stream\WritableStreamInterface|null */
+ protected $stdin;
+
+ /** @var Deferred|null */
+ protected $deferredStdin;
+
+ /** @var \React\Stream\ReadableStreamInterface|null */
+ protected $stdout;
+
+ /** @var Deferred|null */
+ protected $deferredStdout;
+
+ /** @var \React\Stream\ReadableStreamInterface|null */
+ protected $stderr;
+
+ /** @var Deferred|null */
+ protected $deferredStderr;
+
+ public function __construct(IcingaCliRunner $runner = null)
+ {
+ if ($runner === null) {
+ $runner = new IcingaCliRunner();
+ }
+ $this->runner = $runner;
+ $this->init();
+ }
+
+ protected function init()
+ {
+ // Override this if you want.
+ }
+
+ public function setArguments($arguments)
+ {
+ $this->arguments = $arguments;
+
+ return $this;
+ }
+
+ public function getArguments()
+ {
+ return $this->arguments;
+ }
+
+ public function run(LoopInterface $loop)
+ {
+ $process = $this->runner->command($this->getArguments());
+ $canceller = function () use ($process) {
+ // TODO: first soft, then hard
+ $process->terminate();
+ };
+ $deferred = new Deferred($canceller);
+ $process->on('exit', function ($exitCode, $termSignal) use ($deferred) {
+ $state = new FinishedProcessState($exitCode, $termSignal);
+ if ($state->succeeded()) {
+ $deferred->resolve();
+ } else {
+ $deferred->reject(new Exception($state->getReason()));
+ }
+ });
+
+ $process->start($loop);
+ if ($this->deferredStdin instanceof Deferred) {
+ $this->deferredStdin->resolve($process->stdin);
+ } else {
+ $this->stdin = $process->stdin;
+ }
+ if ($this->deferredStdout instanceof Deferred) {
+ $this->deferredStdout->resolve($process->stdout);
+ } else {
+ $this->stdout = $process->stdout;
+ }
+ if ($this->deferredStderr instanceof Deferred) {
+ $this->deferredStderr->resolve($process->stderr);
+ } else {
+ $this->stderr = $process->stderr;
+ }
+ $this->emit('start', [$process]);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @return \React\Stream\WritableStreamInterface
+ */
+ public function stdin()
+ {
+ if ($this->stdin === null) {
+ $this->deferredStdin = new Deferred();
+ $this->stdin = Stream\unwrapWritable($this->deferredStdin->promise());
+ }
+
+ return $this->stdin;
+ }
+
+ /**
+ * @return \React\Stream\ReadableStreamInterface
+ */
+ public function stdout()
+ {
+ if ($this->stdout === null) {
+ $this->deferredStdout = new Deferred();
+ $this->stdout = Stream\unwrapReadable($this->deferredStdout->promise());
+ }
+
+ return $this->stdout;
+ }
+
+ /**
+ * @return \React\Stream\ReadableStreamInterface
+ */
+ public function stderr()
+ {
+ if ($this->stderr === null) {
+ $this->deferredStderr = new Deferred();
+ $this->stderr = Stream\unwrapReadable($this->deferredStderr->promise());
+ }
+
+ return $this->stderr;
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php
new file mode 100644
index 0000000..473d4ed
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRpc.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+use Exception;
+use gipfl\Protocol\JsonRpc\Connection;
+use gipfl\Protocol\NetString\StreamWrapper;
+use React\ChildProcess\Process;
+
+class IcingaCliRpc extends IcingaCli
+{
+ /** @var IcingaCliRunner */
+ protected $runner;
+
+ /** @var Connection|null */
+ protected $rpc;
+
+ protected $arguments = [];
+
+ protected function init()
+ {
+ $this->on('start', function (Process $process) {
+ $netString = new StreamWrapper(
+ $process->stdout,
+ $process->stdin
+ );
+ $netString->on('error', function (Exception $e) {
+ $this->emit('error', [$e]);
+ });
+ $this->rpc()->handle($netString);
+ });
+ }
+
+ /**
+ * @return Connection
+ */
+ public function rpc()
+ {
+ if ($this->rpc === null) {
+ $this->rpc = new Connection();
+ }
+
+ return $this->rpc;
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php
new file mode 100644
index 0000000..60a25cc
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/IcingaCliRunner.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+use React\ChildProcess\Process;
+use gipfl\Cli\Process as CliProcess;
+
+class IcingaCliRunner
+{
+ /** @var string */
+ protected $binary;
+
+ /** @var string|null */
+ protected $cwd;
+
+ /** @var array|null */
+ protected $env;
+
+ public function __construct($binary = null)
+ {
+ if ($binary === null) {
+ $this->binary = CliProcess::getBinaryPath();
+ $this->cwd = CliProcess::getInitialCwd();
+ } else {
+ $this->binary = $binary;
+ }
+ }
+
+ /**
+ * @param mixed array|...$arguments
+ * @return Process
+ */
+ public function command($arguments = null)
+ {
+ if (! \is_array($arguments)) {
+ $arguments = \func_get_args();
+ }
+
+ return new Process(
+ $this->escapedCommand($arguments),
+ $this->cwd,
+ $this->env
+ );
+ }
+
+ /**
+ * @param string|null $cwd
+ */
+ public function setCwd($cwd)
+ {
+ if ($cwd === null) {
+ $this->cwd = $cwd;
+ } else {
+ $this->cwd = (string) $cwd;
+ }
+ }
+
+ /**
+ * @param array|null $env
+ */
+ public function setEnv($env)
+ {
+ if ($env === null) {
+ $this->env = $env;
+ } else {
+ $this->env = (array) $env;
+ }
+ }
+
+ /**
+ * @param $arguments
+ * @return string
+ */
+ protected function escapedCommand($arguments)
+ {
+ $command = ['exec', \escapeshellcmd($this->binary)];
+
+ foreach ($arguments as $argument) {
+ if (\ctype_alnum(preg_replace('/^-{1,2}/', '', $argument))) {
+ $command[] = $argument;
+ } else {
+ $command[] = \escapeshellarg($argument);
+ }
+ }
+
+ return \implode(' ', $command);
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php b/vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php
new file mode 100644
index 0000000..0303b2c
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/RetryUnless.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+use Exception;
+use Icinga\Application\Logger;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+use RuntimeException;
+
+class RetryUnless
+{
+ /** @var LoopInterface */
+ protected $loop;
+
+ /** @var Deferred */
+ protected $deferred;
+
+ /** @var TimerInterface */
+ protected $timer;
+
+ /** @var callable */
+ protected $callback;
+
+ /** @var bool */
+ protected $expectsSuccess;
+
+ /** @var int Regular interval */
+ protected $interval = 1;
+
+ /** @var int|null Optional, interval will be changed after $burst attempts */
+ protected $burst = null;
+
+ /** @var int|null Interval after $burst attempts */
+ protected $finalInterval = null;
+
+ /** @var int Current attempt count */
+ protected $attempts = 0;
+
+ /** @var bool No attempts will be made while paused */
+ protected $paused = false;
+
+ protected $lastError;
+
+ protected function __construct($callback, $expectsSuccess = true)
+ {
+ $this->callback = $callback;
+ $this->expectsSuccess = $expectsSuccess;
+ }
+
+ public static function succeeding($callback)
+ {
+ return new static($callback);
+ }
+
+ public static function failing($callback)
+ {
+ return new static($callback, false);
+ }
+
+ public function run(LoopInterface $loop)
+ {
+ $this->assertNotRunning();
+ $this->deferred = $deferred = new Deferred();
+ $this->loop = $loop;
+ $loop->futureTick(function () {
+ $this->nextAttempt();
+ });
+
+ return $deferred->promise();
+ }
+
+ public function getLastError()
+ {
+ return $this->lastError;
+ }
+
+ public function setInterval($interval)
+ {
+ $this->interval = $interval;
+
+ return $this;
+ }
+
+ public function slowDownAfter($burst, $interval)
+ {
+ $this->burst = $burst;
+ $this->finalInterval = $interval;
+
+ return $this;
+ }
+
+ public function pause()
+ {
+ $this->removeEventualTimer();
+ $this->paused = true;
+
+ return $this;
+ }
+
+ public function resume()
+ {
+ if ($this->paused) {
+ $this->paused = false;
+ if ($this->timer === null) {
+ $this->nextAttempt();
+ }
+ }
+ }
+
+ public function reset()
+ {
+ $this->attempts = 0;
+ $this->paused = false;
+ $this->removeEventualTimer();
+ $this->rejectEventualDeferred('RetryUnless has been reset');
+
+ return $this;
+ }
+
+ public function getAttempts()
+ {
+ return $this->attempts;
+ }
+
+ protected function nextAttempt()
+ {
+ if ($this->paused) {
+ return;
+ }
+
+ $this->removeEventualTimer();
+ $this->attempts++;
+ try {
+ $callback = $this->callback;
+ $this->handleResult($callback());
+ } catch (Exception $e) {
+ $this->handleResult($e);
+ }
+ }
+
+ protected function logError(Exception $e)
+ {
+ if ($this->lastError !== $e->getMessage()) {
+ $this->lastError = $e->getMessage();
+ Logger::error($e);
+ }
+ }
+
+ protected function handleResult($result)
+ {
+ if ($this->expectsSuccess) {
+ if ($result instanceof Exception) {
+ $this->logError($result);
+ $this->scheduleNextAttempt();
+ } elseif ($result instanceof PromiseInterface) {
+ $later = function ($result) {
+ $this->handleResult($result);
+ };
+ $result->then($later, $later);
+ } else {
+ $this->succeed($result);
+ }
+ } else {
+ if ($result instanceof Exception) {
+ $this->succeed($result);
+ } else {
+ $this->scheduleNextAttempt();
+ }
+ }
+ }
+
+ protected function scheduleNextAttempt()
+ {
+ if ($this->timer !== null) {
+ throw new RuntimeException(
+ 'RetryUnless schedules next attempt while already scheduled'
+ );
+ }
+ $this->timer = $this->loop->addTimer($this->getNextInterval(), function () {
+ $this->nextAttempt();
+ });
+ }
+
+ protected function succeed($result)
+ {
+ $this->removeEventualTimer();
+ if ($this->deferred === null) {
+ Logger::warning('RetryUnless tries to resolve twice');
+
+ return;
+ }
+ $this->deferred->resolve($result);
+ $this->deferred = null;
+ $this->reset();
+ }
+
+ protected function getNextInterval()
+ {
+ if ($this->burst === null) {
+ return $this->interval;
+ }
+
+ return $this->attempts >= $this->burst
+ ? $this->finalInterval
+ : $this->interval;
+ }
+
+ protected function assertNotRunning()
+ {
+ if ($this->deferred) {
+ throw new RuntimeException(
+ 'Cannot re-run RetryUnless while already running'
+ );
+ }
+ }
+
+ protected function removeEventualTimer()
+ {
+ if ($this->timer) {
+ $this->loop->cancelTimer($this->timer);
+ $this->timer = null;
+ }
+ }
+
+ protected function rejectEventualDeferred($reason)
+ {
+ if ($this->deferred !== null) {
+ $deferred = $this->deferred;
+ $this->deferred = null;
+ $deferred->reject($reason);
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->removeEventualTimer();
+ $this->rejectEventualDeferred('RetryUnless has been destructed');
+
+ $this->loop = null;
+ }
+}
diff --git a/vendor/gipfl/icinga-cli-daemon/src/StateMachine.php b/vendor/gipfl/icinga-cli-daemon/src/StateMachine.php
new file mode 100644
index 0000000..d8ec470
--- /dev/null
+++ b/vendor/gipfl/icinga-cli-daemon/src/StateMachine.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace gipfl\IcingaCliDaemon;
+
+use RuntimeException;
+
+trait StateMachine
+{
+ /** @var string */
+ private $currentState;
+
+ /** @var array [fromState][toState] = [callback, ...] */
+ private $allowedTransitions = [];
+
+ /** @var array [state] = [callback, ...] */
+ private $onState = [];
+
+ public function initializeStateMachine($initialState)
+ {
+ if ($this->currentState !== null) {
+ throw new RuntimeException('StateMachine has already been initialized');
+ }
+ $this->currentState = $initialState;
+ }
+
+ /**
+ * @param string|array $fromState
+ * @param string $toState
+ * @param callable $callback
+ * @return $this
+ */
+ public function onTransition($fromState, $toState, $callback)
+ {
+ if (is_array($fromState)) {
+ foreach ($fromState as $state) {
+ $this->onTransition($state, $toState, $callback);
+ }
+ } else {
+ $this->allowTransition($fromState, $toState);
+ $this->allowedTransitions[$fromState][$toState][] = $callback;
+ }
+
+ return $this;
+ }
+
+ public function allowTransition($fromState, $toState)
+ {
+ if (! isset($this->allowedTransitions[$fromState][$toState])) {
+ $this->allowedTransitions[$fromState][$toState] = [];
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $state
+ * @param $callback
+ * @return $this
+ */
+ public function onState($state, $callback)
+ {
+ if (! isset($this->onState[$state])) {
+ $this->onState[$state] = [];
+ }
+
+ $this->onState[$state][] = $callback;
+
+ return $this;
+ }
+
+ public function getState()
+ {
+ if ($this->currentState === null) {
+ throw new RuntimeException('StateMachine has not been initialized');
+ }
+
+ return $this->currentState;
+ }
+
+ public function setState($state)
+ {
+ $fromState = $this->getState();
+ if ($this->canTransit($fromState, $state)) {
+ $this->currentState = $state;
+ $this->runStateTransition($fromState, $state);
+ } else {
+ throw new RuntimeException(sprintf(
+ 'A transition from %s to %s is not allowed',
+ $fromState,
+ $state
+ ));
+ }
+ }
+
+ private function runStateTransition($fromState, $toState)
+ {
+ if (isset($this->allowedTransitions[$fromState][$toState])) {
+ foreach ($this->allowedTransitions[$fromState][$toState] as $callback) {
+ $callback();
+ }
+ }
+ if (isset($this->onState[$toState])) {
+ foreach ($this->onState[$toState] as $callback) {
+ $callback();
+ }
+ }
+ }
+
+ public function canTransit($fromState, $toState)
+ {
+ return isset($this->allowedTransitions[$fromState][$toState]);
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/composer.json b/vendor/gipfl/icingaweb2/composer.json
new file mode 100644
index 0000000..25a757f
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/composer.json
@@ -0,0 +1,21 @@
+{
+ "name": "gipfl/icingaweb2",
+ "type": "library",
+ "description": "Helpers and glue for Icinga Web 2",
+ "homepage": "https://github.com/gipfl/icingaweb2",
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\IcingaWeb2\\": "src/"
+ }
+ },
+ "require": {
+ "php": ">=5.6",
+ "ext-ctype": "*",
+ "gipfl/format": ">=0.3",
+ "gipfl/translation": ">=0.1",
+ "ipl/html": ">=0.2.1"
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/public/css/11-action-bar.less b/vendor/gipfl/icingaweb2/public/css/11-action-bar.less
new file mode 100644
index 0000000..ab3d737
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/public/css/11-action-bar.less
@@ -0,0 +1,105 @@
+div.gipfl-action-bar {
+ > * {
+ vertical-align: middle;
+ }
+ .pagination-control {
+ float: none;
+ clear: none;
+ display: inline-block;
+ line-height: inherit;
+ margin: 0;
+ }
+
+ form input {
+ margin: 0;
+ }
+}
+
+div.gipfl-action-bar a, div.gipfl-action-bar form i {
+ color: @icinga-blue;
+}
+
+div.gipfl-action-bar a.badge {
+ color: inherit;
+}
+
+div.gipfl-action-bar > a {
+ margin-right: 1em;
+}
+
+#layout.twocols div.gipfl-action-bar .pagination-control {
+ li {
+ display: none;
+ }
+
+ li:nth-child(1), li.active, li:last-child {
+ display: list-item;
+ }
+
+ li.active a {
+ border-bottom: none;
+ }
+}
+
+/** Dropdown in action bar **/
+div.gipfl-action-bar ul {
+ padding: 0;
+ margin: 0;
+
+ li {
+ list-style-type: none;
+ a { display: block; }
+ }
+}
+
+div.gipfl-action-bar > ul {
+ display: inline-block;
+ > li {
+ display: inline-block;
+ }
+ ul {
+ padding: 0.5em 1em 0 0;
+ min-width: 10em;
+ position: absolute;
+ display: none;
+ background-color: @menu-flyout-bg-color;
+ border: 1px solid @gray-light;
+ box-shadow: 0.3em 0.3em 0.3em 0 rgba(0, 0, 0, 0.2);
+
+ a {
+ display: block;
+ padding: 0.3em 0.5em 0.3em 1em;
+ margin: 0;
+ outline: none;
+ color: @menu-flyout-color;
+
+ &:hover {
+ text-decoration: underline;
+ &:before {
+ text-decoration: none;
+ }
+ }
+ }
+
+ li.active a {
+ font-weight: bold;
+ }
+ }
+ > li:hover ul {
+ display: block;
+ z-index: 2;
+ }
+ > li > a {
+ padding: 0.2em 0.5em;
+ }
+ > li:hover {
+ background-color: @tr-active-color;
+ border-top-left-radius: 0.1em;
+ border-top-right-radius: 0.1em;
+ & > a {
+ color: @icinga-blue;
+ text-decoration: none;
+ }
+ }
+}
+/** END of dropdown in action bar **/
diff --git a/vendor/gipfl/icingaweb2/public/css/12-quicksearch.less b/vendor/gipfl/icingaweb2/public/css/12-quicksearch.less
new file mode 100644
index 0000000..a5da7e9
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/public/css/12-quicksearch.less
@@ -0,0 +1,17 @@
+form.gipfl-quicksearch {
+ display: block;
+ input.search {
+ width: 8em;
+ min-width: unset;
+ border: none;
+ background-color: inherit;
+ padding-left: 2em;
+ margin-left: 1.5em;
+ font-size: 0.75em;
+ font-weight: normal;
+ &:focus {
+ width: 16em;
+ border: none;
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/CompatController.php b/vendor/gipfl/icingaweb2/src/CompatController.php
new file mode 100644
index 0000000..952b4b5
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/CompatController.php
@@ -0,0 +1,581 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use gipfl\IcingaWeb2\Controller\Extension\AutoRefreshHelper;
+use gipfl\IcingaWeb2\Controller\Extension\ConfigHelper;
+use gipfl\Translation\StaticTranslator;
+use gipfl\Translation\TranslationHelper;
+use gipfl\IcingaWeb2\Controller\Extension\ControlsAndContentHelper;
+use gipfl\IcingaWeb2\Widget\ControlsAndContent;
+use gipfl\IcingaWeb2\Zf1\SimpleViewRenderer;
+use GuzzleHttp\Psr7\ServerRequest;
+use Icinga\Application\Benchmark;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Manager;
+use Icinga\Application\Modules\Module;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\ProgrammingError;
+use Icinga\File\Pdf;
+use Icinga\Security\SecurityException;
+use Icinga\User;
+use Icinga\Web\Notification;
+use Icinga\Web\Session;
+use Icinga\Web\UrlParams;
+use Icinga\Web\Url as WebUrl;
+use Icinga\Web\Window;
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+use RuntimeException;
+use Zend_Controller_Action;
+use Zend_Controller_Action_HelperBroker as ZfHelperBroker;
+use Zend_Controller_Request_Abstract as ZfRequest;
+use Zend_Controller_Response_Abstract as ZfResponse;
+
+/**
+ * Class CompatController
+ * @method \Icinga\Web\Request getRequest() {
+ * {@inheritdoc}
+ * @return \Icinga\Web\Request
+ * }
+ *
+ * @method \Icinga\Web\Response getResponse() {
+ * {@inheritdoc}
+ * @return \Icinga\Web\Response
+ * }
+ */
+class CompatController extends Zend_Controller_Action implements ControlsAndContent
+{
+ use AutoRefreshHelper;
+ use ControlsAndContentHelper;
+ use ConfigHelper;
+ use TranslationHelper;
+
+ /** @var bool Whether the controller requires the user to be authenticated */
+ protected $requiresAuthentication = true;
+
+ protected $applicationName = 'Icinga Web';
+
+ /** @var string The current module's name */
+ private $moduleName;
+
+ private $module;
+
+ private $window;
+
+ // https://github.com/joshbuchea/HEAD
+
+ /** @var SimpleViewRenderer */
+ protected $viewRenderer;
+
+ /** @var bool */
+ private $reloadCss = false;
+
+ /** @var bool */
+ private $rerenderLayout = false;
+
+ /** @var string */
+ private $xhrLayout = 'inline';
+
+ /** @var \Zend_Layout */
+ private $layout;
+
+ /** @var string The inner layout (inside the body) to use */
+ private $innerLayout = 'body';
+
+ /**
+ * Authentication manager
+ *
+ * @var Auth|null
+ */
+ private $auth;
+
+ /** @var UrlParams */
+ protected $params;
+
+ /**
+ * The constructor starts benchmarking, loads the configuration and sets
+ * other useful controller properties
+ *
+ * @param ZfRequest $request
+ * @param ZfResponse $response
+ * @param array $invokeArgs Any additional invocation arguments
+ * @throws SecurityException
+ */
+ public function __construct(
+ ZfRequest $request,
+ ZfResponse $response,
+ array $invokeArgs = array()
+ ) {
+ /** @var \Icinga\Web\Request $request */
+ /** @var \Icinga\Web\Response $response */
+ $this->setRequest($request)
+ ->setResponse($response)
+ ->_setInvokeArgs($invokeArgs);
+
+ $this->prepareViewRenderer();
+ $this->_helper = new ZfHelperBroker($this);
+
+ $this->handlerBrowserWindows();
+ $this->initializeTranslator();
+ $this->initializeLayout();
+
+ $this->view->compact = $request->getParam('view') === 'compact';
+ $url = $this->url();
+ $this->params = $url->getParams();
+
+ if ($url->shift('showCompact')) {
+ $this->view->compact = true;
+ }
+
+ $this->checkPermissionBasics();
+ Benchmark::measure('Ready to initialize the controller');
+ $this->prepareInit();
+ $this->init();
+ }
+
+ protected function initializeLayout()
+ {
+ $url = $this->url();
+ $layout = $this->layout = $this->_helper->layout();
+ $layout->isIframe = $url->shift('isIframe');
+ $layout->showFullscreen = $url->shift('showFullscreen');
+ $layout->moduleName = $this->getModuleName();
+ if ($this->rerenderLayout = $url->shift('renderLayout')) {
+ $this->xhrLayout = $this->innerLayout;
+ }
+ if ($url->shift('_disableLayout')) {
+ $this->layout->disableLayout();
+ }
+ }
+
+ /**
+ * Prepare controller initialization
+ *
+ * As it should not be required for controllers to call the parent's init() method,
+ * base controllers should use prepareInit() in order to prepare the controller
+ * initialization.
+ *
+ * @see \Zend_Controller_Action::init() For the controller initialization method.
+ */
+ protected function prepareInit()
+ {
+ }
+
+ /**
+ * Return the current module's name
+ *
+ * @return string
+ */
+ public function getModuleName()
+ {
+ if ($this->moduleName === null) {
+ $this->moduleName = $this->getRequest()->getModuleName();
+ }
+
+ return $this->moduleName;
+ }
+
+ protected function setModuleName($name)
+ {
+ $this->moduleName = $name;
+
+ return $this;
+ }
+
+ /**
+ * Return this controller's module
+ *
+ * @return Module
+ * @codingStandardsIgnoreStart
+ */
+ public function Module()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->module === null) {
+ try {
+ $this->module = Icinga::app()->getModuleManager()->getModule($this->getModuleName());
+ } catch (ProgrammingError $e) {
+ throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ return $this->module;
+ }
+
+ /**
+ * @return Window
+ * @codingStandardsIgnoreStart
+ */
+ public function Window()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->window === null) {
+ $this->window = new Window(
+ $this->getRequestHeader('X-Icinga-WindowId', Window::UNDEFINED)
+ );
+ }
+ return $this->window;
+ }
+
+ protected function handlerBrowserWindows()
+ {
+ if ($this->isXhr()) {
+ $id = $this->getRequestHeader('X-Icinga-WindowId', Window::UNDEFINED);
+
+ if ($id === Window::UNDEFINED) {
+ $this->window = new Window($id);
+ $this->_response->setHeader('X-Icinga-WindowId', Window::generateId());
+ }
+ }
+ }
+
+ /**
+ * @return ServerRequestInterface
+ */
+ protected function getServerRequest()
+ {
+ return ServerRequest::fromGlobals();
+ }
+
+ protected function getRequestHeader($key, $default = null)
+ {
+ try {
+ $value = $this->getRequest()->getHeader($key);
+ } catch (\Zend_Controller_Request_Exception $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ if ($value === null) {
+ return $default;
+ } else {
+ return $value;
+ }
+ }
+
+ /**
+ * @throws SecurityException
+ */
+ protected function checkPermissionBasics()
+ {
+ $request = $this->getRequest();
+ // $auth->authenticate($request, $response, $this->requiresLogin());
+ if ($this->requiresLogin()) {
+ if (! $request->isXmlHttpRequest() && $request->isApiRequest()) {
+ Auth::getInstance()->challengeHttp();
+ }
+ $this->redirectToLogin(Url::fromRequest());
+ }
+ if (($this->Auth()->isAuthenticated() || $this->requiresLogin())
+ && $this->getFrontController()->getDefaultModule() !== $this->getModuleName()) {
+ $this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->getModuleName());
+ }
+ }
+
+ protected function initializeTranslator()
+ {
+ $moduleName = $this->getModuleName();
+ $domain = $moduleName !== 'default' ? $moduleName : 'icinga';
+ $this->view->translationDomain = $domain;
+ StaticTranslator::set(new Translator($domain));
+ }
+
+ public function init()
+ {
+ // Hint: we intentionally do not call our parent's init() method
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return Auth
+ * @codingStandardsIgnoreStart
+ */
+ public function Auth()
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->auth === null) {
+ $this->auth = Auth::getInstance();
+ }
+ return $this->auth;
+ }
+
+ /**
+ * Whether the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @return bool
+ */
+ public function hasPermission($permission)
+ {
+ return $this->Auth()->hasPermission($permission);
+ }
+
+ /**
+ * Assert that the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @throws SecurityException If the current user lacks the given permission
+ */
+ public function assertPermission($permission)
+ {
+ if (! $this->Auth()->hasPermission($permission)) {
+ throw new SecurityException('No permission for %s', $permission);
+ }
+ }
+
+ /**
+ * Return restriction information for an eventually authenticated user
+ *
+ * @param string $name Restriction name
+ *
+ * @return array
+ */
+ public function getRestrictions($name)
+ {
+ return $this->Auth()->getRestrictions($name);
+ }
+
+ /**
+ * Check whether the controller requires a login. That is when the controller requires authentication and the
+ * user is currently not authenticated
+ *
+ * @return bool
+ */
+ protected function requiresLogin()
+ {
+ if (! $this->requiresAuthentication) {
+ return false;
+ }
+
+ return ! $this->Auth()->isAuthenticated();
+ }
+
+ public function prepareViewRenderer()
+ {
+ $this->viewRenderer = new SimpleViewRenderer();
+ $this->viewRenderer->replaceZendViewRenderer();
+ $this->view = $this->viewRenderer->view;
+ }
+
+ /**
+ * @return SimpleViewRenderer
+ */
+ public function getViewRenderer()
+ {
+ return $this->viewRenderer;
+ }
+
+ protected function redirectXhr($url)
+ {
+ if (! $url instanceof WebUrl) {
+ $url = Url::fromPath($url);
+ }
+
+ if ($this->rerenderLayout) {
+ $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
+ }
+ if ($this->reloadCss) {
+ $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
+ }
+
+ $this->shutdownSession();
+
+ $this->getResponse()
+ ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()))
+ ->sendHeaders();
+
+ exit;
+ }
+
+ /**
+ * Detect whether the current request requires changes in the layout and apply them before rendering
+ *
+ * @see Zend_Controller_Action::postDispatch()
+ */
+ public function postDispatch()
+ {
+ Benchmark::measure('Action::postDispatch()');
+
+ $layout = $this->layout;
+ $req = $this->getRequest();
+ $layout->innerLayout = $this->innerLayout;
+
+ /** @var User $user */
+ if ($user = $req->getUser()) {
+ if ((bool) $user->getPreferences()->getValue('icingaweb', 'show_benchmark', false)) {
+ if ($layout->isEnabled()) {
+ $layout->benchmark = $this->renderBenchmark();
+ }
+ }
+
+ if (! (bool) $user->getPreferences()->getValue('icingaweb', 'auto_refresh', true)) {
+ $this->disableAutoRefresh();
+ }
+ }
+
+ if ($req->getParam('format') === 'pdf') {
+ $this->shutdownSession();
+ $this->sendAsPdf();
+ return;
+ }
+
+ if ($this->isXhr()) {
+ $this->postDispatchXhr();
+ }
+
+ $this->shutdownSession();
+ }
+
+ public function postDispatchXhr()
+ {
+ $this->layout->setLayout($this->xhrLayout);
+ $resp = $this->getResponse();
+
+ $notifications = Notification::getInstance();
+ if ($notifications->hasMessages()) {
+ $notificationList = array();
+ foreach ($notifications->popMessages() as $m) {
+ $notificationList[] = rawurlencode($m->type . ' ' . $m->message);
+ }
+ $resp->setHeader('X-Icinga-Notification', implode('&', $notificationList), true);
+ }
+
+ if ($this->reloadCss) {
+ $resp->setHeader('X-Icinga-CssReload', 'now', true);
+ }
+
+ if ($this->title) {
+ if (preg_match('~[\r\n]~', $this->title)) {
+ // TODO: Innocent exception and error log for hack attempts
+ throw new InvalidArgumentException('No newlines allowed in page title');
+ }
+ $resp->setHeader(
+ 'X-Icinga-Title',
+ // TODO: Config
+ rawurlencode($this->title . ' :: ' . $this->applicationName),
+ true
+ );
+ } else {
+ // TODO: config
+ $resp->setHeader('X-Icinga-Title', rawurlencode($this->applicationName), true);
+ }
+
+ if ($this->rerenderLayout) {
+ $this->getResponse()->setHeader('X-Icinga-Container', 'layout', true);
+ }
+
+ if (isset($this->autorefreshInterval)) {
+ $resp->setHeader('X-Icinga-Refresh', $this->autorefreshInterval, true);
+ }
+
+ if ($name = $this->getModuleName()) {
+ $this->getResponse()->setHeader('X-Icinga-Module', $name, true);
+ }
+ }
+
+ /**
+ * Redirect to login
+ *
+ * XHR will always redirect to __SELF__ if an URL to redirect to after successful login is set. __SELF__ instructs
+ * JavaScript to redirect to the current window's URL if it's an auto-refresh request or to redirect to the URL
+ * which required login if it's not an auto-refreshing one.
+ *
+ * XHR will respond with HTTP status code 403 Forbidden.
+ *
+ * @param Url|string $redirect URL to redirect to after successful login
+ */
+ protected function redirectToLogin($redirect = null)
+ {
+ $login = Url::fromPath('authentication/login');
+ if ($this->isXhr()) {
+ if ($redirect !== null) {
+ $login->setParam('redirect', '__SELF__');
+ }
+
+ $this->_response->setHttpResponseCode(403);
+ } elseif ($redirect !== null) {
+ if (! $redirect instanceof Url) {
+ $redirect = Url::fromPath($redirect);
+ }
+
+ if (($relativeUrl = $redirect->getRelativeUrl())) {
+ $login->setParam('redirect', $relativeUrl);
+ }
+ }
+
+ $this->rerenderLayout()->redirectNow($login);
+ }
+
+ protected function sendAsPdf()
+ {
+ $pdf = new Pdf();
+ $pdf->renderControllerAction($this);
+ }
+
+ /**
+ * Render the benchmark
+ *
+ * @return string Benchmark HTML
+ */
+ protected function renderBenchmark()
+ {
+ $this->viewRenderer->postDispatch();
+ Benchmark::measure('Response ready');
+ return Benchmark::renderToHtml()/*
+ . '<pre style="height: 16em; vertical-overflow: scroll">'
+ . print_r(get_included_files(), 1)
+ . '</pre>'*/;
+ }
+
+ public function isXhr()
+ {
+ return $this->getRequest()->isXmlHttpRequest();
+ }
+
+ protected function redirectHttp($url)
+ {
+ if (! $url instanceof Url) {
+ $url = Url::fromPath($url);
+ }
+ $this->shutdownSession();
+ $this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
+ }
+
+ /**
+ * Redirect to a specific url, updating the browsers URL field
+ *
+ * @param Url|string $url The target to redirect to
+ **/
+ public function redirectNow($url)
+ {
+ if ($this->isXhr()) {
+ $this->redirectXhr($url);
+ } else {
+ $this->redirectHttp($url);
+ }
+ }
+
+ protected function shutdownSession()
+ {
+ $session = Session::getSession();
+ if ($session->hasChanged()) {
+ $session->write();
+ }
+ }
+
+ protected function rerenderLayout()
+ {
+ $this->rerenderLayout = true;
+ $this->xhrLayout = 'layout';
+ return $this;
+ }
+
+ protected function reloadCss()
+ {
+ $this->reloadCss = true;
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php b/vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php
new file mode 100644
index 0000000..5b899ba
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Controller/Extension/AutoRefreshHelper.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Controller\Extension;
+
+use InvalidArgumentException;
+
+trait AutoRefreshHelper
+{
+ /** @var int|null */
+ private $autorefreshInterval;
+
+ public function setAutorefreshInterval($interval)
+ {
+ if (! is_int($interval) || $interval < 1) {
+ throw new InvalidArgumentException(
+ 'Setting autorefresh interval smaller than 1 second is not allowed'
+ );
+ }
+ $this->autorefreshInterval = $interval;
+ $this->layout->autorefreshInterval = $interval;
+ return $this;
+ }
+
+ public function disableAutoRefresh()
+ {
+ $this->autorefreshInterval = null;
+ $this->layout->autorefreshInterval = null;
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php b/vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php
new file mode 100644
index 0000000..72cefa7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Controller/Extension/ConfigHelper.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Controller\Extension;
+
+use Icinga\Application\Config;
+
+trait ConfigHelper
+{
+ private $config;
+
+ private $configs = [];
+
+ /**
+ * @param null $file
+ * @return Config
+ * @codingStandardsIgnoreStart
+ */
+ public function Config($file = null)
+ {
+ // @codingStandardsIgnoreEnd
+ if ($this->moduleName === null) {
+ if ($file === null) {
+ return Config::app();
+ } else {
+ return Config::app($file);
+ }
+ } else {
+ return $this->getModuleConfig($file);
+ }
+ }
+
+ public function getModuleConfig($file = null)
+ {
+ if ($file === null) {
+ if ($this->config === null) {
+ $this->config = Config::module($this->getModuleName());
+ }
+ return $this->config;
+ } else {
+ if (! array_key_exists($file, $this->configs)) {
+ $this->configs[$file] = Config::module($this->getModuleName(), $file);
+ }
+ return $this->configs[$file];
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php b/vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php
new file mode 100644
index 0000000..00ef389
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Controller/Extension/ControlsAndContentHelper.php
@@ -0,0 +1,173 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Controller\Extension;
+
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\Content;
+use gipfl\IcingaWeb2\Widget\Controls;
+use gipfl\IcingaWeb2\Widget\Tabs;
+use ipl\Html\HtmlDocument;
+
+trait ControlsAndContentHelper
+{
+ /** @var Controls */
+ private $controls;
+
+ /** @var Content */
+ private $content;
+
+ protected $title;
+
+ /** @var Url */
+ private $url;
+
+ /** @var Url */
+ private $originalUrl;
+
+ /**
+ * TODO: Not sure whether we need dedicated Content/Controls classes,
+ * a simple Container with a class name might suffice here
+ *
+ * @return Controls
+ */
+ public function controls()
+ {
+ if ($this->controls === null) {
+ $this->view->controls = $this->controls = new Controls();
+ }
+
+ return $this->controls;
+ }
+
+ /**
+ * @param Tabs|null $tabs
+ * @return Tabs
+ */
+ public function tabs(Tabs $tabs = null)
+ {
+ if ($tabs === null) {
+ return $this->controls()->getTabs();
+ } else {
+ $this->controls()->setTabs($tabs);
+ return $tabs;
+ }
+ }
+
+ /**
+ * @param HtmlDocument|null $actionBar
+ * @return HtmlDocument
+ */
+ public function actions(HtmlDocument $actionBar = null)
+ {
+ if ($actionBar === null) {
+ return $this->controls()->getActionBar();
+ } else {
+ $this->controls()->setActionBar($actionBar);
+ return $actionBar;
+ }
+ }
+
+ /**
+ * @return Content
+ */
+ public function content()
+ {
+ if ($this->content === null) {
+ $this->view->content = $this->content = new Content();
+ }
+
+ return $this->content;
+ }
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $this->makeTitle(func_get_args());
+ return $this;
+ }
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function addTitle($title)
+ {
+ $title = $this->makeTitle(func_get_args());
+ $this->title = $title;
+ $this->controls()->addTitle($title);
+
+ return $this;
+ }
+
+ private function makeTitle($args)
+ {
+ $title = array_shift($args);
+
+ if (empty($args)) {
+ return $title;
+ } else {
+ return vsprintf($title, $args);
+ }
+ }
+
+ /**
+ * @param string $title
+ * @param mixed $url
+ * @param string $name
+ * @return $this
+ */
+ public function addSingleTab($title, $url = null, $name = 'main')
+ {
+ if ($url === null) {
+ $url = $this->url();
+ }
+
+ $this->tabs()->add($name, [
+ 'label' => $title,
+ 'url' => $url,
+ ])->activate($name);
+
+ return $this;
+ }
+
+ /**
+ * @return Url
+ */
+ public function url()
+ {
+ if ($this->url === null) {
+ $this->url = $this->getOriginalUrl();
+ }
+
+ return $this->url;
+ }
+
+ /**
+ * @return Url
+ */
+ public function getOriginalUrl()
+ {
+ if ($this->originalUrl === null) {
+ $this->originalUrl = clone($this->getUrlFromRequest());
+ }
+
+ return clone($this->originalUrl);
+ }
+
+ /**
+ * @return Url
+ */
+ protected function getUrlFromRequest()
+ {
+ /** @var \Icinga\Web\Request $request */
+ $request = $this->getRequest();
+ $webUrl = $request->getUrl();
+
+ return Url::fromPath(
+ $webUrl->getPath()
+ )->setParams($webUrl->getParams());
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Data/Paginatable.php b/vendor/gipfl/icingaweb2/src/Data/Paginatable.php
new file mode 100644
index 0000000..8cd3963
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Data/Paginatable.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Data;
+
+use Countable;
+
+interface Paginatable extends Countable
+{
+ /**
+ * Set a limit count and offset
+ *
+ * @param int $count Number of rows to return
+ * @param int $offset Skip that many rows
+ *
+ * @return self
+ */
+ public function limit($count = null, $offset = null);
+
+ /**
+ * Whether a limit is set
+ *
+ * @return bool
+ */
+ public function hasLimit();
+
+ /**
+ * Get the limit if any
+ *
+ * @return int|null
+ */
+ public function getLimit();
+
+ /**
+ * Set limit
+ *
+ * @param int $limit Number of rows to return
+ *
+ * @return int|null
+ */
+ public function setLimit($limit);
+
+ /**
+ * Whether an offset is set
+ *
+ * @return bool
+ */
+ public function hasOffset();
+
+ /**
+ * Get the offset if any
+ *
+ * @return int|null
+ */
+ public function getOffset();
+
+ /**
+ * Set offset
+ *
+ * @param int $offset Skip that many rows
+ *
+ * @return int|null
+ */
+ public function setOffset($offset);
+}
diff --git a/vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php b/vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php
new file mode 100644
index 0000000..d6a1b97
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Data/SimpleQueryPaginationAdapter.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Data;
+
+use Icinga\Application\Benchmark;
+use Icinga\Data\SimpleQuery;
+
+class SimpleQueryPaginationAdapter implements Paginatable
+{
+ /** @var SimpleQuery */
+ private $query;
+
+ public function __construct(SimpleQuery $query)
+ {
+ $this->query = $query;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ Benchmark::measure('Running count() for pagination');
+ $count = $this->query->count();
+ Benchmark::measure("Counted $count rows");
+
+ return $count;
+ }
+
+ public function limit($count = null, $offset = null)
+ {
+ $this->query->limit($count, $offset);
+ }
+
+ public function hasLimit()
+ {
+ return $this->getLimit() !== null;
+ }
+
+ public function getLimit()
+ {
+ return $this->query->getLimit();
+ }
+
+ public function setLimit($limit)
+ {
+ $this->query->limit(
+ $limit,
+ $this->getOffset()
+ );
+ }
+
+ public function hasOffset()
+ {
+ return $this->getOffset() !== null;
+ }
+
+ public function getOffset()
+ {
+ return $this->query->getOffset();
+ }
+
+ public function setOffset($offset)
+ {
+ $this->query->limit(
+ $this->getLimit(),
+ $offset
+ );
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/FakeRequest.php b/vendor/gipfl/icingaweb2/src/FakeRequest.php
new file mode 100644
index 0000000..854c26e
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/FakeRequest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Icinga\Web\Request;
+use RuntimeException;
+
+class FakeRequest extends Request
+{
+ /** @var string */
+ private static $baseUrl;
+
+ public static function setConfiguredBaseUrl($url)
+ {
+ self::$baseUrl = $url;
+ }
+
+ public function setUrl(Url $url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ public function getBaseUrl($raw = false)
+ {
+ if (self::$baseUrl === null) {
+ throw new RuntimeException('Cannot determine base URL on CLI if not configured');
+ } else {
+ return self::$baseUrl;
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Icon.php b/vendor/gipfl/icingaweb2/src/Icon.php
new file mode 100644
index 0000000..0001e12
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Icon.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use ipl\Html\BaseHtmlElement;
+
+class Icon extends BaseHtmlElement
+{
+ protected $tag = 'i';
+
+ public function __construct($name, $attributes = null)
+ {
+ $this->setAttributes($attributes);
+ $this->getAttributes()->add('class', array('icon', 'icon-' . $name));
+ }
+
+ /**
+ * @param string $name
+ * @param array $attributes
+ *
+ * @return static
+ */
+ public static function create($name, array $attributes = null)
+ {
+ return new static($name, $attributes);
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/IconHelper.php b/vendor/gipfl/icingaweb2/src/IconHelper.php
new file mode 100644
index 0000000..1c8af9d
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/IconHelper.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use InvalidArgumentException;
+
+/**
+ * Icon helper class
+ *
+ * Should help to reduce redundant icon-lookup code. Currently with hardcoded
+ * icons only, could easily provide support for all of them as follows:
+ *
+ * $confFile = Icinga::app()
+ * ->getApplicationDir('fonts/fontello-ifont/config.json');
+ *
+ * $font = json_decode(file_get_contents($confFile));
+ * // 'icon-' is to be found in $font->css_prefix_text
+ * foreach ($font->glyphs as $icon) {
+ * // $icon->css (= 'help') -> 0x . dechex($icon->code)
+ * }
+ */
+class IconHelper
+{
+ private $icons = [
+ 'minus' => 'e806',
+ 'trash' => 'e846',
+ 'plus' => 'e805',
+ 'cancel' => 'e804',
+ 'help' => 'e85b',
+ 'angle-double-right' => 'e87b',
+ 'up-big' => 'e825',
+ 'down-big' => 'e828',
+ 'down-open' => 'e821',
+ ];
+
+ private $mappedUtf8Icons;
+
+ private $reversedUtf8Icons;
+
+ private static $instance;
+
+ public function __construct()
+ {
+ $this->prepareIconMappings();
+ }
+
+ public static function instance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new static;
+ }
+
+ return self::$instance;
+ }
+
+ public function characterIconName($character)
+ {
+ if (array_key_exists($character, $this->reversedUtf8Icons)) {
+ return $this->reversedUtf8Icons[$character];
+ } else {
+ throw new InvalidArgumentException('There is no mapping for the given character');
+ }
+ }
+
+ protected function hexToCharacter($hex)
+ {
+ return json_decode('"\u' . $hex . '"');
+ }
+
+ public function iconCharacter($name)
+ {
+ if (array_key_exists($name, $this->mappedUtf8Icons)) {
+ return $this->mappedUtf8Icons[$name];
+ } else {
+ return $this->mappedUtf8Icons['help'];
+ }
+ }
+
+ protected function prepareIconMappings()
+ {
+ $this->mappedUtf8Icons = [];
+ $this->reversedUtf8Icons = [];
+ foreach ($this->icons as $name => $hex) {
+ $character = $this->hexToCharacter($hex);
+ $this->mappedUtf8Icons[$name] = $character;
+ $this->reversedUtf8Icons[$character] = $name;
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Img.php b/vendor/gipfl/icingaweb2/src/Img.php
new file mode 100644
index 0000000..3c68adb
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Img.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Icinga\Web\Url as WebUrl;
+use ipl\Html\Attribute;
+use ipl\Html\BaseHtmlElement;
+
+class Img extends BaseHtmlElement
+{
+ protected $tag = 'img';
+
+ /** @var Url */
+ protected $url;
+
+ protected $defaultAttributes = array('alt' => '');
+
+ protected function __construct()
+ {
+ }
+
+ /**
+ * @param Url|string $url
+ * @param array $urlParams
+ * @param array $attributes
+ *
+ * @return static
+ */
+ public static function create($url, $urlParams = null, array $attributes = null)
+ {
+ /** @var Img $img */
+ $img = new static();
+ $img->setAttributes($attributes);
+ $img->getAttributes()->registerAttributeCallback('src', array($img, 'getSrcAttribute'));
+ $img->setUrl($url, $urlParams);
+ return $img;
+ }
+
+ public function setUrl($url, $urlParams)
+ {
+ if ($url instanceof WebUrl) { // Hint: Url is also a WebUrl
+ if ($urlParams !== null) {
+ $url->addParams($urlParams);
+ }
+
+ $this->url = $url;
+ } else {
+ if ($urlParams === null) {
+ if (is_string($url) && substr($url, 0, 5) === 'data:') {
+ $this->url = $url;
+ return;
+ } else {
+ $this->url = Url::fromPath($url);
+ }
+ } else {
+ $this->url = Url::fromPath($url, $urlParams);
+ }
+ }
+
+ $this->url->getParams();
+ }
+
+ /**
+ * @return Attribute
+ */
+ public function getSrcAttribute()
+ {
+ if (is_string($this->url)) {
+ return new Attribute('src', $this->url);
+ } else {
+ return new Attribute('src', $this->getUrl()->getAbsoluteUrl('&'));
+ }
+ }
+
+ /**
+ * @return Url
+ */
+ public function getUrl()
+ {
+ // TODO: What if null? #?
+ return $this->url;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Link.php b/vendor/gipfl/icingaweb2/src/Link.php
new file mode 100644
index 0000000..e6e4de9
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Link.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Icinga\Web\Url as WebUrl;
+use ipl\Html\Attribute;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\ValidHtml;
+
+class Link extends BaseHtmlElement
+{
+ protected $tag = 'a';
+
+ /** @var Url */
+ protected $url;
+
+ /**
+ * Link constructor.
+ * @param $content
+ * @param $url
+ * @param null $urlParams
+ * @param array|null $attributes
+ */
+ public function __construct($content, $url, $urlParams = null, array $attributes = null)
+ {
+ $this->setContent($content);
+ $this->setAttributes($attributes);
+ $this->getAttributes()->registerAttributeCallback('href', array($this, 'getHrefAttribute'));
+ $this->setUrl($url, $urlParams);
+ }
+
+ /**
+ * @param ValidHtml|array|string $content
+ * @param Url|string $url
+ * @param array $urlParams
+ * @param mixed $attributes
+ *
+ * @return static
+ */
+ public static function create($content, $url, $urlParams = null, array $attributes = null)
+ {
+ $link = new static($content, $url, $urlParams, $attributes);
+ return $link;
+ }
+
+ /**
+ * @param $url
+ * @param $urlParams
+ */
+ public function setUrl($url, $urlParams)
+ {
+ if ($url instanceof WebUrl) { // Hint: Url is also a WebUrl
+ if ($urlParams !== null) {
+ $url->addParams($urlParams);
+ }
+
+ $this->url = $url;
+ } else {
+ if ($urlParams === null) {
+ $this->url = Url::fromPath($url);
+ } else {
+ $this->url = Url::fromPath($url, $urlParams);
+ }
+ }
+
+ $this->url->getParams();
+ }
+
+ /**
+ * @return Attribute
+ */
+ public function getHrefAttribute()
+ {
+ return new Attribute('href', $this->getUrl()->getAbsoluteUrl('&'));
+ }
+
+ /**
+ * @return Url
+ */
+ public function getUrl()
+ {
+ // TODO: What if null? #?
+ return $this->url;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php b/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php
new file mode 100644
index 0000000..7a5a3ff
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/Extension/MultiSelect.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table\Extension;
+
+use gipfl\IcingaWeb2\Url;
+
+// Could also be a static method, MultiSelect::enable($table)
+trait MultiSelect
+{
+ protected function enableMultiSelect($url, $sourceUrl, array $keys)
+ {
+ /** @var $table \ipl\Html\BaseHtmlElement */
+ $table = $this;
+ $table->addAttributes([
+ 'class' => 'multiselect'
+ ]);
+
+ $prefix = 'data-icinga-multiselect';
+ $multi = [
+ "$prefix-url" => Url::fromPath($url),
+ "$prefix-controllers" => Url::fromPath($sourceUrl),
+ "$prefix-data" => implode(',', $keys),
+ ];
+
+ $table->addAttributes($multi);
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php b/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php
new file mode 100644
index 0000000..0f0cec3
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/Extension/QuickSearch.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table\Extension;
+
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\Controls;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+trait QuickSearch
+{
+ /** @var BaseHtmlElement */
+ private $quickSearchForm;
+
+ public function getQuickSearch(BaseHtmlElement $parent, Url $url)
+ {
+ $this->requireQuickSearchForm($parent, $url);
+ $search = $url->getParam('q');
+ return $search;
+ }
+
+ private function requireQuickSearchForm(BaseHtmlElement $parent, Url $url)
+ {
+ if ($this->quickSearchForm === null) {
+ $this->quickSearchForm = $this->buildQuickSearchForm($parent, $url);
+ }
+ }
+
+ private function buildQuickSearchForm(BaseHtmlElement $parent, Url $url)
+ {
+ $search = $url->getParam('q');
+
+ $form = Html::tag('form', [
+ 'action' => $url->without(array('q', 'page', 'modifyFilter'))->getAbsoluteUrl(),
+ 'class' => ['gipfl-quicksearch'],
+ 'method' => 'GET'
+ ]);
+
+ $form->add(
+ Html::tag('input', [
+ 'type' => 'text',
+ 'name' => 'q',
+ 'title' => $this->translate('Search is simple! Try to combine multiple words'),
+ 'value' => $search,
+ 'placeholder' => $this->translate('Search...'),
+ 'class' => 'search'
+ ])
+ );
+
+ $this->addQuickSearchToControls($parent, $form);
+
+ return $form;
+ }
+
+ protected function addQuickSearchToControls(BaseHtmlElement $parent, BaseHtmlElement $form)
+ {
+ if ($parent instanceof Controls) {
+ $title = $parent->getTitleElement();
+ if ($title === null) {
+ $parent->prepend($form);
+ } else {
+ $input = $form->getFirst('input');
+ $form->remove($input);
+ $title->add($input);
+ $form->add($title);
+ $parent->setTitleElement($form);
+ }
+ } else {
+ $parent->prepend($form);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php b/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php
new file mode 100644
index 0000000..cb1eac6
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/Extension/ZfSortablePriority.php
@@ -0,0 +1,263 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table\Extension;
+
+use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
+use gipfl\IcingaWeb2\IconHelper;
+use gipfl\ZfDb\Exception\SelectException;
+use gipfl\ZfDb\Select;
+use Icinga\Web\Request;
+use Icinga\Web\Response;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlString;
+use RuntimeException;
+use Zend_Db_Select_Exception as ZfDbSelectException;
+
+/**
+ * Trait ZfSortablePriority
+ *
+ * Assumes to run in a ZfQueryBasedTable
+ */
+trait ZfSortablePriority
+{
+ /** @var Request */
+ protected $request;
+
+ /** @var Response */
+ protected $response;
+
+ public function handleSortPriorityActions(Request $request, Response $response)
+ {
+ $this->request = $request;
+ $this->response = $response;
+ return $this;
+ }
+
+ protected function reallyHandleSortPriorityActions()
+ {
+ $request = $this->request;
+
+ if ($request->isPost() && $this->hasBeenSent($request)) {
+ // $this->fixPriorities();
+ foreach (array_keys($request->getPost()) as $key) {
+ if (substr($key, 0, 8) === 'MOVE_UP_') {
+ $id = (int) substr($key, 8);
+ $this->moveRow($id, 'up');
+ }
+ if (substr($key, 0, 10) === 'MOVE_DOWN_') {
+ $id = (int) substr($key, 10);
+ $this->moveRow($id, 'down');
+ }
+ }
+ $this->response->redirectAndExit($request->getUrl());
+ }
+ }
+
+ protected function hasBeenSent(Request $request)
+ {
+ return $request->getPost('__FORM_NAME') === $this->getUniqueFormName();
+ }
+
+ protected function addSortPriorityButtons(BaseHtmlElement $tr, $row)
+ {
+ $tr->add(
+ Html::tag(
+ 'td',
+ null,
+ $this->createUpDownButtons($row->{$this->getKeyColumn()})
+ )
+ );
+
+ return $tr;
+ }
+
+ protected function getKeyColumn()
+ {
+ if (isset($this->keyColumn)) {
+ return $this->keyColumn;
+ } else {
+ throw new RuntimeException(
+ 'ZfSortablePriority requires keyColumn'
+ );
+ }
+ }
+
+ protected function getPriorityColumn()
+ {
+ if (isset($this->priorityColumn)) {
+ return $this->priorityColumn;
+ } else {
+ throw new RuntimeException(
+ 'ZfSortablePriority requires priorityColumn'
+ );
+ }
+ }
+
+ protected function getPriorityColumns()
+ {
+ return [
+ 'id' => $this->getKeyColumn(),
+ 'prio' => $this->getPriorityColumn()
+ ];
+ }
+
+ protected function moveRow($id, $direction)
+ {
+ /** @var $this ZfQueryBasedTable */
+ $db = $this->db();
+ /** @var $this ZfQueryBasedTable */
+ $query = $this->getQuery();
+ $tableParts = $this->getQueryPart(Select::FROM);
+ $alias = key($tableParts);
+ $table = $tableParts[$alias]['tableName'];
+
+ $whereParts = $this->getQueryPart(Select::WHERE);
+ unset($query);
+ if (empty($whereParts)) {
+ $where = '';
+ } else {
+ $where = ' AND ' . implode(' ', $whereParts);
+ }
+
+ $prioCol = $this->getPriorityColumn();
+ $keyCol = $this->getKeyColumn();
+ $myPrio = (int) $db->fetchOne(
+ $db->select()
+ ->from($table, $prioCol)
+ ->where("$keyCol = ?", $id)
+ );
+
+ $op = $direction === 'up' ? '<' : '>';
+ $sortDir = $direction === 'up' ? 'DESC' : 'ASC';
+ $query = $db->select()
+ ->from([$alias => $table], $this->getPriorityColumns())
+ ->where("$prioCol $op ?", $myPrio)
+ ->order("$prioCol $sortDir")
+ ->limit(1);
+
+ if (! empty($whereParts)) {
+ $query->where(implode(' ', $whereParts));
+ }
+
+ $next = $db->fetchRow($query);
+
+ if ($next) {
+ $sql = 'UPDATE %s %s'
+ . ' SET %s = CASE WHEN %s = %s THEN %d ELSE %d END'
+ . ' WHERE %s IN (%s, %s)';
+
+ $query = sprintf(
+ $sql,
+ $table,
+ $alias,
+ $prioCol,
+ $keyCol,
+ $id,
+ (int) $next->prio,
+ $myPrio,
+ $keyCol,
+ $id,
+ (int) $next->id
+ ) . $where;
+
+ $db->query($query);
+ }
+ }
+
+ protected function getSortPriorityTitle()
+ {
+ /** @var ZfQueryBasedTable $table */
+ $table = $this;
+
+ return Html::tag(
+ 'span',
+ ['title' => $table->translate('Change priority')],
+ $table->translate('Prio')
+ );
+ }
+
+ protected function createUpDownButtons($key)
+ {
+ /** @var ZfQueryBasedTable $table */
+ $table = $this;
+ $up = $this->createIconButton(
+ "MOVE_UP_$key",
+ 'up-big',
+ $table->translate('Move up (raise priority)')
+ );
+ $down = $this->createIconButton(
+ "MOVE_DOWN_$key",
+ 'down-big',
+ $table->translate('Move down (lower priority)')
+ );
+
+ if ($table->isOnFirstRow()) {
+ $up->getAttributes()->add('disabled', 'disabled');
+ }
+
+ if ($table->isOnLastRow()) {
+ $down->getAttributes()->add('disabled', 'disabled');
+ }
+
+ return [$down, $up];
+ }
+
+ protected function createIconButton($key, $icon, $title)
+ {
+ return Html::tag('input', [
+ 'type' => 'submit',
+ 'class' => 'icon-button',
+ 'name' => $key,
+ 'title' => $title,
+ 'value' => IconHelper::instance()->iconCharacter($icon)
+ ]);
+ }
+
+ protected function getUniqueFormName()
+ {
+ $parts = explode('\\', get_class($this));
+ return end($parts);
+ }
+
+ protected function renderWithSortableForm()
+ {
+ if ($this->request === null) {
+ return parent::render();
+ }
+ $this->reallyHandleSortPriorityActions();
+
+ $url = $this->request->getUrl();
+ // TODO: No margin for form
+ $form = Html::tag('form', [
+ 'action' => $url->getAbsoluteUrl(),
+ 'method' => 'POST'
+ ], [
+ Html::tag('input', [
+ 'type' => 'hidden',
+ 'name' => '__FORM_NAME',
+ 'value' => $this->getUniqueFormName()
+ ]),
+ new HtmlString(parent::render())
+ ]);
+
+ return $form->render();
+ }
+
+ protected function getQueryPart($part)
+ {
+ /** @var ZfQueryBasedTable $table */
+ $table = $this;
+ /** @var Select|\Zend_Db_Select $query */
+ $query = $table->getQuery();
+ try {
+ return $query->getPart($part);
+ } catch (SelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ } catch (ZfDbSelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php
new file mode 100644
index 0000000..e9281c7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/QueryBasedTable.php
@@ -0,0 +1,281 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table;
+
+use Countable;
+use gipfl\Format\LocalDateFormat;
+use gipfl\IcingaWeb2\Data\Paginatable;
+use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer;
+use gipfl\IcingaWeb2\Table\Extension\QuickSearch;
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\ControlsAndContent;
+use gipfl\IcingaWeb2\Widget\Paginator;
+use gipfl\Translation\TranslationHelper;
+use Icinga\Application\Benchmark;
+use Icinga\Data\Filter\Filter;
+use ipl\Html\Table;
+
+abstract class QueryBasedTable extends Table implements Countable
+{
+ use TranslationHelper;
+ use QuickSearch;
+
+ protected $defaultAttributes = [
+ 'class' => ['common-table', 'table-row-selectable'],
+ 'data-base-target' => '_next',
+ ];
+
+ private $fetchedRows;
+
+ private $firstRow;
+
+ private $lastRow = false;
+
+ private $rowNumber;
+
+ private $rowNumberOnPage;
+
+ protected $lastDay;
+
+ /** @var Paginator|null Will usually be defined at rendering time */
+ protected $paginator;
+
+ private $isUsEnglish;
+
+ private $dateFormatter;
+
+ protected $searchColumns = [];
+
+ /**
+ * @return Paginatable
+ */
+ abstract protected function getPaginationAdapter();
+
+ abstract public function getQuery();
+
+ public function getPaginator(Url $url)
+ {
+ return new Paginator(
+ $this->getPaginationAdapter(),
+ $url
+ );
+ }
+
+ public function count()
+ {
+ return $this->getPaginationAdapter()->count();
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ FilterRenderer::applyToQuery($filter, $this->getQuery());
+ return $this;
+ }
+
+ protected function getSearchColumns()
+ {
+ return $this->searchColumns;
+ }
+
+ public function search($search)
+ {
+ if (! empty($search)) {
+ $query = $this->getQuery();
+ $columns = $this->getSearchColumns();
+ if (strpos($search, ' ') === false) {
+ $filter = Filter::matchAny();
+ foreach ($columns as $column) {
+ $filter->addFilter(Filter::expression($column, '=', "*$search*"));
+ }
+ } else {
+ $filter = Filter::matchAll();
+ foreach (explode(' ', $search) as $s) {
+ $sub = Filter::matchAny();
+ foreach ($columns as $column) {
+ $sub->addFilter(Filter::expression($column, '=', "*$s*"));
+ }
+ $filter->addFilter($sub);
+ }
+ }
+
+ FilterRenderer::applyToQuery($filter, $query);
+ }
+
+ return $this;
+ }
+
+ abstract protected function prepareQuery();
+
+ public function renderContent()
+ {
+ $titleColumns = $this->renderTitleColumns();
+ if ($titleColumns) {
+ $this->getHeader()->add($titleColumns);
+ }
+ $this->fetchRows();
+
+ return parent::renderContent();
+ }
+
+ protected function renderTitleColumns()
+ {
+ // TODO: drop this
+ if (method_exists($this, 'getColumnsToBeRendered')) {
+ $columns = $this->getColumnsToBeRendered();
+ if (isset($columns) && count($columns)) {
+ return static::row($columns, null, 'th');
+ }
+ }
+
+ return null;
+ }
+
+ protected function splitByDay($timestamp)
+ {
+ $this->renderDayIfNew((int) $timestamp);
+ }
+
+ public function isOnFirstPage()
+ {
+ if ($this->paginator === null) {
+ // No paginator? Then there should be only a single page
+ return true;
+ }
+
+ return $this->paginator->getCurrentPage() === 1;
+ }
+
+ public function isOnFirstRow()
+ {
+ return $this->firstRow === true;
+ }
+
+ public function isOnLastRow()
+ {
+ return $this->lastRow === true;
+ }
+
+ protected function fetchRows()
+ {
+ $firstPage = $this->isOnFirstPage();
+ $this->rowNumberOnPage = 0;
+ $this->rowNumber = $this->getPaginationAdapter()->getOffset();
+ $lastRow = count($this);
+ foreach ($this->fetch() as $row) {
+ $this->rowNumber++;
+ $this->rowNumberOnPage++;
+ if (null === $this->firstRow) {
+ if ($firstPage) {
+ $this->firstRow = true;
+ } else {
+ $this->firstRow = false;
+ }
+ } elseif (true === $this->firstRow) {
+ $this->firstRow = false;
+ }
+ if ($lastRow === $this->rowNumber) {
+ $this->lastRow = true;
+ }
+ // Hint: do not fetch the body first, the row might want to replace it
+ $tr = $this->renderRow($row);
+ $this->add($tr);
+ }
+ }
+
+ protected function renderRow($row)
+ {
+ return $this::row([$row]);
+ }
+
+ /**
+ * @deprecated
+ * @return bool
+ */
+ protected function isUsEnglish()
+ {
+ if ($this->isUsEnglish === null) {
+ $this->isUsEnglish = in_array(setlocale(LC_ALL, 0), ['en_US', 'en_US.UTF-8', 'C']);
+ }
+
+ return $this->isUsEnglish;
+ }
+
+ /**
+ * @param int $timestamp
+ */
+ protected function renderDayIfNew($timestamp)
+ {
+ $day = $this->getDateFormatter()->getFullDay($timestamp);
+
+ if ($this->lastDay !== $day) {
+ $this->nextHeader()->add(
+ $this::th($day, [
+ 'colspan' => 2,
+ 'class' => 'table-header-day'
+ ])
+ );
+
+ $this->lastDay = $day;
+ $this->nextBody();
+ }
+ }
+
+ abstract protected function fetchQueryRows();
+
+ public function fetch()
+ {
+ $parts = explode('\\', get_class($this));
+ $name = end($parts);
+ Benchmark::measure("Fetching data for $name table");
+ $rows = $this->fetchQueryRows();
+ $this->fetchedRows = count($rows);
+ Benchmark::measure("Fetched $this->fetchedRows rows for $name table");
+
+ return $rows;
+ }
+
+ protected function initializeOptionalQuickSearch(ControlsAndContent $controller)
+ {
+ $columns = $this->getSearchColumns();
+ if (! empty($columns)) {
+ $this->search(
+ $this->getQuickSearch(
+ $controller->controls(),
+ $controller->url()
+ )
+ );
+ }
+ }
+
+ /**
+ * @param ControlsAndContent $controller
+ * @return $this
+ */
+ public function renderTo(ControlsAndContent $controller)
+ {
+ $url = $controller->url();
+ $c = $controller->content();
+ $this->paginator = $this->getPaginator($url);
+ $this->initializeOptionalQuickSearch($controller);
+ $controller->actions()->add($this->paginator);
+ $c->add($this);
+
+ // TODO: move elsewhere
+ if (method_exists($this, 'dumpSqlQuery')) {
+ if ($url->getParam('format') === 'sql') {
+ $c->prepend($this->dumpSqlQuery($url));
+ }
+ }
+
+ return $this;
+ }
+
+ protected function getDateFormatter()
+ {
+ if ($this->dateFormatter === null) {
+ $this->dateFormatter = new LocalDateFormat();
+ }
+
+ return $this->dateFormatter;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php
new file mode 100644
index 0000000..8d6015a
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/SimpleQueryBasedTable.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table;
+
+use Icinga\Data\SimpleQuery;
+use gipfl\IcingaWeb2\Data\SimpleQueryPaginationAdapter;
+
+abstract class SimpleQueryBasedTable extends QueryBasedTable
+{
+ /** @var SimpleQuery */
+ private $query;
+
+ protected function getPaginationAdapter()
+ {
+ return new SimpleQueryPaginationAdapter($this->getQuery());
+ }
+
+ protected function fetchQueryRows()
+ {
+ return $this->query->fetchAll();
+ }
+
+ /**
+ * @return SimpleQuery
+ */
+ public function getQuery()
+ {
+ if ($this->query === null) {
+ $this->query = $this->prepareQuery();
+ }
+
+ return $this->query;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php b/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php
new file mode 100644
index 0000000..8a421d5
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Table/ZfQueryBasedTable.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Table;
+
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\ControlsAndContent;
+use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer;
+use gipfl\IcingaWeb2\Zf1\Db\SelectPaginationAdapter;
+use gipfl\ZfDb\Adapter\Adapter as Db;
+use gipfl\ZfDb\Select as DbSelect;
+use Icinga\Data\Db\DbConnection;
+use Icinga\Data\Filter\Filter;
+use ipl\Html\DeferredText;
+use ipl\Html\Html;
+use LogicException;
+use Zend_Db_Adapter_Abstract as DbAdapter;
+
+abstract class ZfQueryBasedTable extends QueryBasedTable
+{
+ /** @var ?DbConnection */
+ private $connection;
+
+ /** @var DbAdapter|Db */
+ private $db;
+
+ private $query;
+
+ private $paginationAdapter;
+
+ public function __construct($db)
+ {
+ if ($db instanceof Db || $db instanceof DbAdapter) {
+ $this->db = $db;
+ } elseif ($db instanceof DbConnection) {
+ $this->connection = $db;
+ $this->db = $db->getDbAdapter();
+ } else {
+ throw new LogicException(sprintf(
+ 'Unable to deal with %s db class',
+ get_class($db)
+ ));
+ }
+ }
+
+ public static function show(ControlsAndContent $controller, DbConnection $db)
+ {
+ $table = new static($db);
+ $table->renderTo($controller);
+ }
+
+ public function getCountQuery()
+ {
+ return $this->getPaginationAdapter()->getCountQuery();
+ }
+
+ protected function getPaginationAdapter()
+ {
+ if ($this->paginationAdapter === null) {
+ $this->paginationAdapter = new SelectPaginationAdapter($this->getQuery());
+ }
+
+ return $this->paginationAdapter;
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ FilterRenderer::applyToQuery($filter, $this->getQuery());
+ return $this;
+ }
+
+ public function search($search)
+ {
+ if (! empty($search)) {
+ $query = $this->getQuery();
+ $columns = $this->getSearchColumns();
+ if (strpos($search, ' ') === false) {
+ $filter = Filter::matchAny();
+ foreach ($columns as $column) {
+ $filter->addFilter(Filter::expression($column, '=', "*$search*"));
+ }
+ } else {
+ $filter = Filter::matchAll();
+ foreach (explode(' ', $search) as $s) {
+ $sub = Filter::matchAny();
+ foreach ($columns as $column) {
+ $sub->addFilter(Filter::expression($column, '=', "*$s*"));
+ }
+ $filter->addFilter($sub);
+ }
+ }
+
+ FilterRenderer::applyToQuery($filter, $query);
+ }
+
+ return $this;
+ }
+
+ protected function fetchQueryRows()
+ {
+ return $this->db->fetchAll($this->getQuery());
+ }
+
+ /**
+ * @deprecated Might be null, we'll fade it out
+ * @return ?DbConnection
+ */
+ public function connection()
+ {
+ return $this->connection;
+ }
+
+ public function db()
+ {
+ return $this->db;
+ }
+
+ /**
+ * @return DbSelect|\Zend_Db_Select
+ */
+ public function getQuery()
+ {
+ if ($this->query === null) {
+ $this->query = $this->prepareQuery();
+ }
+
+ return $this->query;
+ }
+
+ public function dumpSqlQuery(Url $url)
+ {
+ $self = $this;
+ return Html::tag('div', ['class' => 'sql-dump'], [
+ Link::create('[ close ]', $url->without('format')),
+ Html::tag('h3', null, $this->translate('SQL Query')),
+ Html::tag('pre', null, new DeferredText(
+ function () use ($self) {
+ return wordwrap($self->getQuery());
+ }
+ )),
+ Html::tag('h3', null, $this->translate('Count Query')),
+ Html::tag('pre', null, new DeferredText(
+ function () use ($self) {
+ return wordwrap($self->getCountQuery());
+ }
+ )),
+ ]);
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Translator.php b/vendor/gipfl/icingaweb2/src/Translator.php
new file mode 100644
index 0000000..b1cb088
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Translator.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use gipfl\Translation\TranslatorInterface;
+
+class Translator implements TranslatorInterface
+{
+ /** @var string */
+ private $domain;
+
+ public function __construct($domain)
+ {
+ $this->domain = $domain;
+ }
+
+ public function translate($string)
+ {
+ $res = dgettext($this->domain, $string);
+ if ($res === $string && $this->domain !== 'icinga') {
+ return dgettext('icinga', $string);
+ }
+
+ return $res;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Url.php b/vendor/gipfl/icingaweb2/src/Url.php
new file mode 100644
index 0000000..2c6bf1f
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Url.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace gipfl\IcingaWeb2;
+
+use Exception;
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Url as WebUrl;
+use Icinga\Web\UrlParams;
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\UriInterface;
+use RuntimeException;
+
+/**
+ * Class Url
+ *
+ * The main purpose of this class is to get unit tests running on CLI
+ * Little code from Icinga\Web\Url has been duplicated, as neither fromPath()
+ * nor getRequest() can be extended in a meaningful way at the time of this
+ * writing
+ */
+class Url extends WebUrl
+{
+ /**
+ * @param string $url
+ * @param array $params
+ * @param null $request
+ * @return Url
+ */
+ public static function fromPath($url, array $params = array(), $request = null)
+ {
+ if ($request === null) {
+ $request = static::getRequest();
+ }
+
+ if (! \is_string($url)) {
+ throw new InvalidArgumentException(sprintf(
+ 'url "%s" is not a string',
+ \var_export($url, 1)
+ ));
+ }
+
+ $self = new static;
+
+ if ($url === '#') {
+ return $self->setPath($url);
+ }
+
+ $parts = \parse_url($url);
+
+ $self->setBasePath($request->getBaseUrl());
+ if (isset($parts['path'])) {
+ $self->setPath($parts['path']);
+ }
+
+ if (isset($parts['query'])) {
+ $params = UrlParams::fromQueryString($parts['query'])->mergeValues($params);
+ }
+
+ if (isset($parts['fragment'])) {
+ $self->setAnchor($parts['fragment']);
+ }
+
+ $self->setParams($params);
+ return $self;
+ }
+
+ public static function fromUri(UriInterface $uri)
+ {
+ $query = $uri->getQuery();
+ $path = $uri->getPath();
+ if (\strlen($query)) {
+ $path .= "?$query";
+ }
+
+ return static::fromPath($path);
+ }
+
+ public static function fromServerRequest(ServerRequestInterface $request)
+ {
+ return static::fromUri($request->getUri());
+ }
+
+ /**
+ * Create a new Url class representing the current request
+ *
+ * If $params are given, those will be added to the request's parameters
+ * and overwrite any existing parameters
+ *
+ * @param UrlParams|array $params Parameters that should additionally be considered for the url
+ * @param \Icinga\Web\Request $request A request to use instead of the default one
+ *
+ * @return Url
+ */
+ public static function fromRequest($params = array(), $request = null)
+ {
+ if ($request === null) {
+ $request = static::getRequest();
+ }
+
+ $url = new Url();
+ $url->setPath(\ltrim($request->getPathInfo(), '/'));
+ $request->getQuery();
+
+ // $urlParams = UrlParams::fromQueryString($request->getQuery());
+ if (isset($_SERVER['QUERY_STRING'])) {
+ $urlParams = UrlParams::fromQueryString($_SERVER['QUERY_STRING']);
+ } else {
+ $urlParams = UrlParams::fromQueryString('');
+ foreach ($request->getQuery() as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ }
+
+ foreach ($params as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ $url->setParams($urlParams);
+ $url->setBasePath($request->getBaseUrl());
+
+ return $url;
+ }
+
+ public function setBasePath($basePath)
+ {
+ if (property_exists($this, 'basePath')) {
+ parent::setBasePath($basePath);
+ } else {
+ $this->setBaseUrl($basePath);
+ }
+
+ return $this;
+ }
+
+ public function setParams($params)
+ {
+ try {
+ return parent::setParams($params);
+ } catch (ProgrammingError $e) {
+ throw new InvalidArgumentException($e->getMessage(), 0, $e);
+ }
+ }
+
+ protected static function getRequest()
+ {
+ try {
+ $app = Icinga::app();
+ } catch (ProgrammingError $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+ if ($app->isCli()) {
+ try {
+ return new FakeRequest();
+ } catch (Exception $e) {
+ throw new RuntimeException($e->getMessage(), 0, $e);
+ }
+ } else {
+ return $app->getRequest();
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/ActionBar.php b/vendor/gipfl/icingaweb2/src/Widget/ActionBar.php
new file mode 100644
index 0000000..63e6c77
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/ActionBar.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\BaseHtmlElement;
+
+class ActionBar extends BaseHtmlElement
+{
+ protected $contentSeparator = ' ';
+
+ /** @var string */
+ protected $tag = 'div';
+
+ protected $defaultAttributes = ['class' => 'gipfl-action-bar'];
+
+ /**
+ * @param string $target
+ * @return $this
+ */
+ public function setBaseTarget($target)
+ {
+ $this->getAttributes()->set('data-base-target', $target);
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Content.php b/vendor/gipfl/icingaweb2/src/Widget/Content.php
new file mode 100644
index 0000000..92ea115
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Content.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\BaseHtmlElement;
+
+class Content extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $contentSeparator = "\n";
+
+ protected $defaultAttributes = ['class' => 'content'];
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Controls.php b/vendor/gipfl/icingaweb2/src/Widget/Controls.php
new file mode 100644
index 0000000..cb52013
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Controls.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+
+class Controls extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $contentSeparator = "\n";
+
+ protected $defaultAttributes = ['class' => 'controls'];
+
+ /** @var Tabs */
+ private $tabs;
+
+ /** @var ActionBar */
+ private $actions;
+
+ /** @var string */
+ private $title;
+
+ /** @var string */
+ private $subTitle;
+
+ /** @var BaseHtmlElement */
+ private $titleElement;
+
+ /**
+ * @param $title
+ * @param null $subTitle
+ * @return $this
+ */
+ public function addTitle($title, $subTitle = null)
+ {
+ $this->title = $title;
+ if ($subTitle !== null) {
+ $this->subTitle = $subTitle;
+ }
+
+ return $this->setTitleElement($this->renderTitleElement());
+ }
+
+ /**
+ * @param BaseHtmlElement $element
+ * @return $this
+ */
+ public function setTitleElement(BaseHtmlElement $element)
+ {
+ if ($this->titleElement !== null) {
+ $this->remove($this->titleElement);
+ }
+
+ $this->titleElement = $element;
+ $this->prepend($element);
+
+ return $this;
+ }
+
+ public function getTitleElement()
+ {
+ return $this->titleElement;
+ }
+
+ /**
+ * @return Tabs
+ */
+ public function getTabs()
+ {
+ if ($this->tabs === null) {
+ $this->tabs = new Tabs();
+ }
+
+ return $this->tabs;
+ }
+
+ /**
+ * @param Tabs $tabs
+ * @return $this
+ */
+ public function setTabs(Tabs $tabs)
+ {
+ $this->tabs = $tabs;
+ return $this;
+ }
+
+ /**
+ * @param Tabs $tabs
+ * @return $this
+ */
+ public function prependTabs(Tabs $tabs)
+ {
+ if ($this->tabs === null) {
+ $this->tabs = $tabs;
+ } else {
+ $current = $this->tabs->getTabs();
+ $this->tabs = $tabs;
+ foreach ($current as $name => $tab) {
+ $this->tabs->add($name, $tab);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return ActionBar
+ */
+ public function getActionBar()
+ {
+ if ($this->actions === null) {
+ $this->setActionBar(new ActionBar());
+ }
+
+ return $this->actions;
+ }
+
+ /**
+ * @param HtmlDocument $actionBar
+ * @return $this
+ */
+ public function setActionBar(HtmlDocument $actionBar)
+ {
+ if ($this->actions !== null) {
+ $this->remove($this->actions);
+ }
+
+ $this->actions = $actionBar;
+ $this->add($actionBar);
+
+ return $this;
+ }
+
+ /**
+ * @return BaseHtmlElement
+ */
+ protected function renderTitleElement()
+ {
+ $h1 = Html::tag('h1', null, $this->title);
+ if ($this->subTitle) {
+ $h1->setSeparator(' ')->add(
+ Html::tag('small', null, $this->subTitle)
+ );
+ }
+
+ return $h1;
+ }
+
+ /**
+ * @return string
+ */
+ public function renderContent()
+ {
+ if (null !== $this->tabs) {
+ $this->prepend($this->tabs);
+ }
+
+ return parent::renderContent();
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php b/vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php
new file mode 100644
index 0000000..8574ce7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/ControlsAndContent.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\HtmlDocument;
+use gipfl\IcingaWeb2\Url;
+
+interface ControlsAndContent
+{
+ /**
+ * @return Controls
+ */
+ public function controls();
+
+ /**
+ * @return Tabs
+ */
+ public function tabs();
+
+ /**
+ * @param HtmlDocument|null $actionBar
+ * @return HtmlDocument
+ */
+ public function actions(HtmlDocument $actionBar = null);
+
+ /**
+ * @return Content
+ */
+ public function content();
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function setTitle($title);
+
+ /**
+ * @param $title
+ * @return $this
+ */
+ public function addTitle($title);
+
+ /**
+ * @param $title
+ * @param null $url
+ * @param string $name
+ * @return $this
+ */
+ public function addSingleTab($title, $url = null, $name = 'main');
+
+ /**
+ * @return Url
+ */
+ public function url();
+
+ /**
+ * @return Url
+ */
+ public function getOriginalUrl();
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/ListItem.php b/vendor/gipfl/icingaweb2/src/Widget/ListItem.php
new file mode 100644
index 0000000..fa4b562
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/ListItem.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\ValidHtml;
+
+class ListItem extends BaseHtmlElement
+{
+ protected $contentSeparator = "\n";
+
+ /**
+ * @param ValidHtml|array|string $content
+ * @param Attributes|array $attributes
+ *
+ * @return $this
+ */
+ public function addItem($content, $attributes = null)
+ {
+ return $this->add(
+ Html::tag('li', $attributes, $content)
+ );
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php b/vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php
new file mode 100644
index 0000000..971a833
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/NameValueTable.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use ipl\Html\Table;
+
+class NameValueTable extends Table
+{
+ protected $defaultAttributes = ['class' => 'name-value-table'];
+
+ public function createNameValueRow($name, $value)
+ {
+ return $this::tr([$this::th($name), $this::td($value)]);
+ }
+
+ public function addNameValueRow($name, $value)
+ {
+ return $this->add($this->createNameValueRow($name, $value));
+ }
+
+ public function addNameValuePairs($pairs)
+ {
+ foreach ($pairs as $name => $value) {
+ $this->addNameValueRow($name, $value);
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Paginator.php b/vendor/gipfl/icingaweb2/src/Widget/Paginator.php
new file mode 100644
index 0000000..3c255a7
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Paginator.php
@@ -0,0 +1,463 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use Icinga\Exception\ProgrammingError;
+use gipfl\IcingaWeb2\Data\Paginatable;
+use gipfl\IcingaWeb2\Icon;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\Translation\TranslationHelper;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+class Paginator extends BaseHtmlElement
+{
+ use TranslationHelper;
+
+ protected $tag = 'div';
+
+ protected $defaultAttributes = [
+ 'class' => 'pagination-control',
+ 'role' => 'navigation',
+ ];
+
+ /** @var Paginatable The query the paginator widget is created for */
+ protected $query;
+
+ /** @var int */
+ protected $pageCount;
+
+ /** @var int */
+ protected $currentCount;
+
+ /** @var Url */
+ protected $url;
+
+ /** @var string */
+ protected $pageParam;
+
+ /** @var string */
+ protected $perPageParam;
+
+ /** @var int */
+ protected $totalCount;
+
+ /** @var int */
+ protected $defaultItemCountPerPage = 25;
+
+ public function __construct(
+ Paginatable $query,
+ Url $url,
+ $pageParameter = 'page',
+ $perPageParameter = 'limit'
+ ) {
+ $this->query = $query;
+ $this->setPageParam($pageParameter);
+ $this->setPerPageParam($perPageParameter);
+ $this->setUrl($url);
+ }
+
+ public function setItemsPerPage($count)
+ {
+ // TODO: this should become setOffset once available
+ $query = $this->getQuery();
+ $query->setLimit($count);
+
+ return $this;
+ }
+
+ protected function setPageParam($pageParam)
+ {
+ $this->pageParam = $pageParam;
+ return $this;
+ }
+
+ protected function setPerPageParam($perPageParam)
+ {
+ $this->perPageParam = $perPageParam;
+ return $this;
+ }
+
+ public function getPageParam()
+ {
+ return $this->pageParam;
+ }
+
+ public function getPerPageParam()
+ {
+ return $this->perPageParam;
+ }
+
+ public function getCurrentPage()
+ {
+ $query = $this->getQuery();
+ if ($query->hasOffset()) {
+ return ($query->getOffset() / $this->getItemsPerPage()) + 1;
+ } else {
+ return 1;
+ }
+ }
+
+ protected function setCurrentPage($page)
+ {
+ $page = (int) $page;
+ $offset = $this->firstRowOnPage($page) - 1;
+ if ($page > 1) {
+ $query = $this->getQuery();
+ $query->setOffset($offset);
+ }
+ }
+
+ public function getPageCount()
+ {
+ if ($this->pageCount === null) {
+ $this->pageCount = (int) ceil($this->getTotalItemCount() / $this->getItemsPerPage());
+ }
+
+ return $this->pageCount;
+ }
+
+ protected function getItemsPerPage()
+ {
+ $limit = $this->getQuery()->getLimit();
+ if ($limit === null) {
+ throw new ProgrammingError('Something went wrong, got no limit when there should be one');
+ } else {
+ return $limit;
+ }
+ }
+
+ public function getTotalItemCount()
+ {
+ if ($this->totalCount === null) {
+ $this->totalCount = count($this->getQuery());
+ }
+
+ return $this->totalCount;
+ }
+
+ public function getPrevious()
+ {
+ if ($this->hasPrevious()) {
+ return $this->getCurrentPage() - 1;
+ } else {
+ return null;
+ }
+ }
+
+ public function hasPrevious()
+ {
+ return $this->getCurrentPage() > 1;
+ }
+
+ public function getNext()
+ {
+ if ($this->hasNext()) {
+ return $this->getCurrentPage() + 1;
+ } else {
+ return null;
+ }
+ }
+
+ public function hasNext()
+ {
+ return $this->getCurrentPage() < $this->getPageCount();
+ }
+
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * Returns an array of "local" pages given the page count and current page number
+ *
+ * @return array
+ */
+ protected function getPages()
+ {
+ $page = $this->getPageCount();
+ $current = $this->getCurrentPage();
+
+ $range = [];
+
+ if ($page < 10) {
+ // Show all pages if we have less than 10
+ for ($i = 1; $i < 10; $i++) {
+ if ($i > $page) {
+ break;
+ }
+
+ $range[$i] = $i;
+ }
+ } else {
+ // More than 10 pages:
+ foreach ([1, 2] as $i) {
+ $range[$i] = $i;
+ }
+
+ if ($current < 6) {
+ // We are on page 1-5 from
+ for ($i = 1; $i <= 7; $i++) {
+ $range[$i] = $i;
+ }
+ } else {
+ // Current page > 5
+ $range[] = '…';
+
+ if (($page - $current) < 5) {
+ // Less than 5 pages left
+ $start = 5 - ($page - $current);
+ } else {
+ $start = 1;
+ }
+
+ for ($i = $current - $start; $i < ($current + (4 - $start)); $i++) {
+ if ($i > $page) {
+ break;
+ }
+
+ $range[$i] = $i;
+ }
+ }
+
+ if ($current < ($page - 2)) {
+ $range[] = '…';
+ }
+
+ foreach ([$page - 1, $page] as $i) {
+ $range[$i] = $i;
+ }
+ }
+
+ if (empty($range)) {
+ $range[] = 1;
+ }
+
+ return $range;
+ }
+
+ public function getDefaultItemCountPerPage()
+ {
+ return $this->defaultItemCountPerPage;
+ }
+
+ public function setDefaultItemCountPerPage($count)
+ {
+ $this->defaultItemCountPerPage = (int) $count;
+ return $this;
+ }
+
+ public function setUrl(Url $url)
+ {
+ $page = (int) $url->shift($this->getPageParam());
+ $perPage = (int) $url->getParam($this->getPerPageParam());
+ if ($perPage > 0) {
+ $this->setItemsPerPage($perPage);
+ } else {
+ if (! $this->getQuery()->hasLimit()) {
+ $this->setItemsPerPage($this->getDefaultItemCountPerPage());
+ }
+ }
+ if ($page > 0) {
+ $this->setCurrentPage($page);
+ }
+
+ $this->url = $url;
+
+ return $this;
+ }
+
+ public function getUrl()
+ {
+ if ($this->url === null) {
+ $this->setUrl(Url::fromRequest());
+ }
+
+ return $this->url;
+ }
+
+ public function getPreviousLabel()
+ {
+ return $this->getLabel($this->getCurrentPage() - 1);
+ }
+
+ protected function getNextLabel()
+ {
+ return $this->getLabel($this->getCurrentPage() + 1);
+ }
+
+ protected function getLabel($page)
+ {
+ return sprintf(
+ $this->translate('Show rows %u to %u of %u'),
+ $this->firstRowOnPage($page),
+ $this->lastRowOnPage($page),
+ $this->getTotalItemCount()
+ );
+ }
+
+ protected function renderPrevious()
+ {
+ return Html::tag('li', [
+ 'class' => 'nav-item'
+ ], Link::create(
+ Icon::create('angle-double-left'),
+ $this->makeUrl($this->getPrevious()),
+ null,
+ [
+ 'title' => $this->getPreviousLabel(),
+ 'class' => 'previous-page'
+ ]
+ ));
+ }
+
+ protected function renderNoPrevious()
+ {
+ return $this->renderDisabled(Html::tag('span', [
+ 'class' => 'previous-page'
+ ], [
+ $this->srOnly($this->translate('Previous page')),
+ Icon::create('angle-double-left')
+ ]));
+ }
+
+ protected function renderNext()
+ {
+ return Html::tag('li', [
+ 'class' => 'nav-item'
+ ], Link::create(
+ Icon::create('angle-double-right'),
+ $this->makeUrl($this->getNext()),
+ null,
+ [
+ 'title' => $this->getNextLabel(),
+ 'class' => 'next-page'
+ ]
+ ));
+ }
+
+ protected function renderNoNext()
+ {
+ return $this->renderDisabled(Html::tag('span', [
+ 'class' => 'previous-page'
+ ], [
+ $this->srOnly($this->translate('Next page')),
+ Icon::create('angle-double-right')
+ ]));
+ }
+
+ protected function renderDots()
+ {
+ return $this->renderDisabled(Html::tag('span', null, '…'));
+ }
+
+ protected function renderInnerPages()
+ {
+ $pages = [];
+ $current = $this->getCurrentPage();
+
+ foreach ($this->getPages() as $page) {
+ if ($page === '…') {
+ $pages[] = $this->renderDots();
+ } else {
+ $pages[] = Html::tag(
+ 'li',
+ $page === $current ? ['class' => 'active'] : null,
+ $this->makeLink($page)
+ );
+ }
+ }
+
+ return $pages;
+ }
+
+ protected function lastRowOnPage($page)
+ {
+ $perPage = $this->getItemsPerPage();
+ $total = $this->getTotalItemCount();
+ $last = $page * $perPage;
+ if ($last > $total) {
+ $last = $total;
+ }
+
+ return $last;
+ }
+
+ protected function firstRowOnPage($page)
+ {
+ return ($page - 1) * $this->getItemsPerPage() + 1;
+ }
+
+ protected function makeLink($page)
+ {
+ return Link::create(
+ $page,
+ $this->makeUrl($page),
+ null,
+ ['title' => $this->getLabel($page)]
+ );
+ }
+
+ protected function makeUrl($page)
+ {
+ if ($page) {
+ return $this->getUrl()->with('page', $page);
+ } else {
+ return $this->getUrl();
+ }
+ }
+
+ protected function srOnly($content)
+ {
+ return Html::tag('span', ['class' => 'sr-only'], $content);
+ }
+
+ protected function renderDisabled($content)
+ {
+ return Html::tag('li', [
+ 'class' => ['nav-item', 'disabled'],
+ 'aria-hidden' => 'true'
+ ], $content);
+ }
+
+ protected function renderList()
+ {
+ return Html::tag(
+ 'ul',
+ ['class' => ['nav', 'tab-nav']],
+ [
+ $this->hasPrevious() ? $this->renderPrevious() : $this->renderNoPrevious(),
+ $this->renderInnerPages(),
+ $this->hasNext() ? $this->renderNext() : $this->renderNoNext()
+ ]
+ );
+ }
+
+ public function assemble()
+ {
+ $this->add([
+ $this->renderScreenReaderHeader(),
+ $this->renderList()
+ ]);
+ }
+
+ protected function renderScreenReaderHeader()
+ {
+ return Html::tag('h2', [
+ // 'id' => $this->protectId('pagination') -> why?
+ 'class' => 'sr-only',
+ 'tab-index' => '-1'
+ ], $this->translate('Pagination'));
+ }
+
+ public function render()
+ {
+ if ($this->getPageCount() < 2) {
+ return '';
+ } else {
+ return parent::render();
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Widget/Tabs.php b/vendor/gipfl/icingaweb2/src/Widget/Tabs.php
new file mode 100644
index 0000000..38bf4cd
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Widget/Tabs.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Widget;
+
+use Exception;
+use Icinga\Web\Widget\Tabs as WebTabs;
+use InvalidArgumentException;
+use ipl\Html\ValidHtml;
+
+class Tabs extends WebTabs implements ValidHtml
+{
+ /**
+ * @param string $name
+ * @return $this
+ */
+ public function activate($name)
+ {
+ try {
+ parent::activate($name);
+ } catch (Exception $e) {
+ throw new InvalidArgumentException(
+ "Can't activate '$name', there is no such tab"
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param array|\Icinga\Web\Widget\Tab $tab
+ * @return $this
+ */
+ public function add($name, $tab)
+ {
+ try {
+ parent::add($name, $tab);
+ } catch (Exception $e) {
+ throw new InvalidArgumentException($e->getMessage());
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php b/vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php
new file mode 100644
index 0000000..07204b8
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/Db/CountQuery.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1\Db;
+
+use gipfl\ZfDb\Select;
+use RuntimeException;
+use Zend_Db_Select as ZfSelect;
+
+class CountQuery
+{
+ /** @var Select|ZfSelect */
+ private $query;
+
+ private $maxRows;
+
+ /**
+ * ZfCountQuery constructor.
+ * @param Select|ZfSelect $query
+ */
+ public function __construct($query)
+ {
+ if ($query instanceof Select || $query instanceof ZfSelect) {
+ $this->query = $query;
+ } else {
+ throw new RuntimeException('Got no supported ZF1 Select object');
+ }
+ }
+
+ public function setMaxRows($max)
+ {
+ $this->maxRows = $max;
+ return $this;
+ }
+
+ public function getQuery()
+ {
+ if ($this->needsSubQuery()) {
+ return $this->buildSubQuery();
+ } else {
+ return $this->buildSimpleQuery();
+ }
+ }
+
+ protected function hasOneOf($parts)
+ {
+ foreach ($parts as $part) {
+ if ($this->hasPart($part)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function hasPart($part)
+ {
+ $values = $this->query->getPart($part);
+ return ! empty($values);
+ }
+
+ protected function needsSubQuery()
+ {
+ return null !== $this->maxRows || $this->hasOneOf([
+ Select::GROUP,
+ Select::UNION
+ ]);
+ }
+
+ protected function buildSubQuery()
+ {
+ $sub = clone($this->query);
+ $sub->limit(null, null);
+ $class = $this->query;
+ $query = new $class($this->query->getAdapter());
+ $query->from($sub, ['cnt' => 'COUNT(*)']);
+ if (null !== $this->maxRows) {
+ $sub->limit($this->maxRows + 1);
+ }
+
+ return $query;
+ }
+
+ protected function buildSimpleQuery()
+ {
+ $query = clone($this->query);
+ $query->reset(Select::COLUMNS);
+ $query->reset(Select::ORDER);
+ $query->reset(Select::LIMIT_COUNT);
+ $query->reset(Select::LIMIT_OFFSET);
+ $query->columns(['cnt' => 'COUNT(*)']);
+ return $query;
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php b/vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php
new file mode 100644
index 0000000..b51296f
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/Db/FilterRenderer.php
@@ -0,0 +1,335 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1\Db;
+
+use gipfl\ZfDb\Adapter\Adapter as Db;
+use gipfl\ZfDb\Exception\SelectException;
+use gipfl\ZfDb\Expr;
+use gipfl\ZfDb\Select;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterAnd;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Filter\FilterNot;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use InvalidArgumentException;
+use RuntimeException;
+use Zend_Db_Adapter_Abstract as DbAdapter;
+use Zend_Db_Expr as DbExpr;
+use Zend_Db_Select as DbSelect;
+use Zend_Db_Select_Exception as DbSelectException;
+
+class FilterRenderer
+{
+ private $db;
+
+ /** @var Filter */
+ private $filter;
+
+ /** @var array */
+ private $columnMap;
+
+ /** @var string */
+ private $dbExprClass;
+
+ /**
+ * FilterRenderer constructor.
+ * @param Filter $filter
+ * @param Db|DbAdapter $db
+ */
+ public function __construct(Filter $filter, $db)
+ {
+ $this->filter = $filter;
+ if ($db instanceof Db) {
+ $this->db = $db;
+ $this->dbExprClass = Expr::class;
+ } elseif ($db instanceof DbAdapter) {
+ $this->db = $db;
+ $this->dbExprClass = DbExpr::class;
+ } else {
+ throw new RuntimeException('Got no supported ZF1 DB adapter');
+ }
+ }
+
+ /**
+ * @return Expr|DbExpr
+ */
+ public function toDbExpression()
+ {
+ return $this->expr($this->render());
+ }
+
+ /**
+ * @return Expr|DbExpr
+ */
+ protected function expr($content)
+ {
+ $class = $this->dbExprClass;
+ return new $class($content);
+ }
+
+ /**
+ * @param Filter $filter
+ * @param Select|DbSelect|SimpleQuery $query
+ * @return Select|DbSelect|SimpleQuery
+ */
+ public static function applyToQuery(Filter $filter, $query)
+ {
+ if ($query instanceof SimpleQuery) {
+ $query->applyFilter($filter);
+ return $query;
+ }
+ if (! ($query instanceof Select || $query instanceof DbSelect)) {
+ throw new RuntimeException('Got no supported ZF1 Select object');
+ }
+
+ if (! $filter->isEmpty()) {
+ $renderer = new static($filter, $query->getAdapter());
+ $renderer->extractColumnMap($query);
+ $query->where($renderer->toDbExpression());
+ }
+
+ return $query;
+ }
+
+ protected function lookupColumnAlias($column)
+ {
+ if (array_key_exists($column, $this->columnMap)) {
+ return $this->columnMap[$column];
+ } else {
+ return $column;
+ }
+ }
+
+ protected function extractColumnMap($query)
+ {
+ $map = [];
+ try {
+ $columns = $query->getPart(Select::COLUMNS);
+ } catch (SelectException $e) {
+ // Will not happen.
+ throw new RuntimeException($e->getMessage());
+ } catch (DbSelectException $e) {
+ // Will not happen.
+ throw new RuntimeException($e->getMessage());
+ }
+
+ foreach ($columns as $col) {
+ if ($col[1] instanceof Expr || $col[1] instanceof DbExpr) {
+ $map[$col[2]] = (string) $col[1];
+ $map[$col[2]] = $col[1];
+ } else {
+ $map[$col[2]] = $col[0] . '.' . $col[1];
+ }
+ }
+
+ $this->columnMap = $map;
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ return $this->renderFilter($this->filter);
+ }
+
+ protected function renderFilterChain(FilterChain $filter, $level = 0)
+ {
+ $prefix = '';
+
+ if ($filter instanceof FilterAnd) {
+ $op = ' AND ';
+ } elseif ($filter instanceof FilterOr) {
+ $op = ' OR ';
+ } elseif ($filter instanceof FilterNot) {
+ $op = ' AND ';
+ $prefix = 'NOT ';
+ } else {
+ throw new InvalidArgumentException(
+ 'Cannot render a %s filter chain for Zf Db',
+ get_class($filter)
+ );
+ }
+
+ $parts = [];
+ if ($filter->isEmpty()) {
+ // Hint: we might want to fail here
+ return '';
+ } else {
+ foreach ($filter->filters() as $f) {
+ $part = $this->renderFilter($f, $level + 1);
+ if ($part !== '') {
+ $parts[] = $part;
+ }
+ }
+ if (empty($parts)) {
+ // will not happen, as we are not empty
+ return '';
+ } else {
+ if ($level > 0) {
+ return "$prefix (" . implode($op, $parts) . ')';
+ } else {
+ return $prefix . implode($op, $parts);
+ }
+ }
+ }
+ }
+
+ protected function renderFilterExpression(FilterExpression $filter)
+ {
+ $col = $this->lookupColumnAlias($filter->getColumn());
+ if (! $col instanceof Expr && ! $col instanceof DbExpr && ! ctype_digit($col)) {
+ $col = $this->db->quoteIdentifier($col);
+ }
+ $sign = $filter->getSign();
+ $expression = $filter->getExpression();
+
+ if (is_array($expression)) {
+ return $this->renderArrayExpression($col, $sign, $expression);
+ }
+
+ if ($sign === '=') {
+ if (strpos($expression, '*') === false) {
+ return $this->renderAny($col, $sign, $expression);
+ } else {
+ return $this->renderLike($col, $expression);
+ }
+ }
+
+ if ($sign === '!=') {
+ if (strpos($expression, '*') === false) {
+ return $this->renderAny($col, $sign, $expression);
+ } else {
+ return $this->renderNotLike($col, $expression);
+ }
+ }
+
+ return $this->renderAny($col, $sign, $expression);
+ }
+
+
+ protected function renderLike($col, $expression)
+ {
+ if ($expression === '*') {
+ return $this->expr('TRUE');
+ }
+
+ return $col . ' LIKE ' . $this->escape($this->escapeWildcards($expression));
+ }
+
+ protected function renderNotLike($col, $expression)
+ {
+ if ($expression === '*') {
+ return $this->expr('FALSE');
+ }
+
+ return sprintf(
+ '(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
+ $col,
+ $this->escape($this->escapeWildcards($expression))
+ );
+ }
+
+ protected function renderNotEqual($col, $expression)
+ {
+ return sprintf('(%1$s != %2$s OR %1$s IS NULL)', $col, $this->escape($expression));
+ }
+
+ protected function renderAny($col, $sign, $expression)
+ {
+ return sprintf('%s %s %s', $col, $sign, $this->escape($expression));
+ }
+
+ protected function renderArrayExpression($col, $sign, $expression)
+ {
+ if ($sign === '=') {
+ return $col . ' IN (' . $this->escape($expression) . ')';
+ } elseif ($sign === '!=') {
+ return sprintf(
+ '(%1$s NOT IN (%2$s) OR %1$s IS NULL)',
+ $col,
+ $this->escape($expression)
+ );
+ }
+
+ throw new InvalidArgumentException(
+ 'Array expressions can only be rendered for = and !=, got %s',
+ $sign
+ );
+ }
+
+ /**
+ * @param Filter $filter
+ * @param int $level
+ * @return string|Expr|DbExpr
+ */
+ protected function renderFilter(Filter $filter, $level = 0)
+ {
+ if ($filter instanceof FilterChain) {
+ return $this->renderFilterChain($filter, $level);
+ } elseif ($filter instanceof FilterExpression) {
+ return $this->renderFilterExpression($filter);
+ } else {
+ throw new RuntimeException(sprintf(
+ 'Filter of type FilterChain or FilterExpression expected, got %s',
+ get_class($filter)
+ ));
+ }
+ }
+
+ protected function escape($value)
+ {
+ // bindParam? bindValue?
+ if (is_array($value)) {
+ $ret = [];
+ foreach ($value as $val) {
+ $ret[] = $this->escape($val);
+ }
+ return implode(', ', $ret);
+ } else {
+ return $this->db->quote($value);
+ }
+ }
+
+ protected function escapeWildcards($value)
+ {
+ return preg_replace('/\*/', '%', $value);
+ }
+
+ public function whereToSql($col, $sign, $expression)
+ {
+ if (is_array($expression)) {
+ if ($sign === '=') {
+ return $col . ' IN (' . $this->escape($expression) . ')';
+ } elseif ($sign === '!=') {
+ return sprintf('(%1$s NOT IN (%2$s) OR %1$s IS NULL)', $col, $this->escape($expression));
+ }
+
+ throw new InvalidArgumentException(
+ 'Unable to render array expressions with operators other than equal or not equal'
+ );
+ } elseif ($sign === '=' && strpos($expression, '*') !== false) {
+ if ($expression === '*') {
+ return $this->expr('TRUE');
+ }
+
+ return $col . ' LIKE ' . $this->escape($this->escapeWildcards($expression));
+ } elseif ($sign === '!=' && strpos($expression, '*') !== false) {
+ if ($expression === '*') {
+ return $this->expr('FALSE');
+ }
+
+ return sprintf(
+ '(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
+ $col,
+ $this->escape($this->escapeWildcards($expression))
+ );
+ } elseif ($sign === '!=') {
+ return sprintf('(%1$s %2$s %3$s OR %1$s IS NULL)', $col, $sign, $this->escape($expression));
+ } else {
+ return sprintf('%s %s %s', $col, $sign, $this->escape($expression));
+ }
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php b/vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php
new file mode 100644
index 0000000..599a3ee
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/Db/SelectPaginationAdapter.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1\Db;
+
+use gipfl\IcingaWeb2\Data\Paginatable;
+use gipfl\ZfDb\Select;
+use gipfl\ZfDb\Exception\SelectException;
+use Icinga\Application\Benchmark;
+use RuntimeException;
+use Zend_Db_Select as ZfSelect;
+use Zend_Db_Select_Exception as ZfDbSelectException;
+
+class SelectPaginationAdapter implements Paginatable
+{
+ private $query;
+
+ private $countQuery;
+
+ private $cachedCount;
+
+ private $cachedCountQuery;
+
+ public function __construct($query)
+ {
+ if ($query instanceof Select || $query instanceof ZfSelect) {
+ $this->query = $query;
+ } else {
+ throw new RuntimeException('Got no supported ZF1 Select object');
+ }
+ }
+
+ public function getCountQuery()
+ {
+ if ($this->countQuery === null) {
+ $this->countQuery = (new CountQuery($this->query))->getQuery();
+ }
+
+ return $this->countQuery;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ $queryString = (string) $this->getCountQuery();
+ if ($this->cachedCountQuery !== $queryString) {
+ Benchmark::measure('Running count() for pagination');
+ $this->cachedCountQuery = $queryString;
+ $count = $this->query->getAdapter()->fetchOne(
+ $queryString
+ );
+ $this->cachedCount = $count;
+ Benchmark::measure("Counted $count rows");
+ }
+
+ return $this->cachedCount;
+ }
+
+ public function limit($count = null, $offset = null)
+ {
+ $this->query->limit($count, $offset);
+ }
+
+ public function hasLimit()
+ {
+ return $this->getLimit() !== null;
+ }
+
+ public function getLimit()
+ {
+ return $this->getQueryPart(Select::LIMIT_COUNT);
+ }
+
+ public function setLimit($limit)
+ {
+ $this->query->limit(
+ $limit,
+ $this->getOffset()
+ );
+ }
+
+ public function hasOffset()
+ {
+ return $this->getOffset() !== null;
+ }
+
+ public function getOffset()
+ {
+ return $this->getQueryPart(Select::LIMIT_OFFSET);
+ }
+
+ protected function getQueryPart($part)
+ {
+ try {
+ return $this->query->getPart($part);
+ } catch (SelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ } catch (ZfDbSelectException $e) {
+ // Will not happen if $part is correct.
+ throw new RuntimeException($e);
+ }
+ }
+
+ public function setOffset($offset)
+ {
+ $this->query->limit(
+ $this->getLimit(),
+ $offset
+ );
+ }
+}
diff --git a/vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php b/vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php
new file mode 100644
index 0000000..89b36a4
--- /dev/null
+++ b/vendor/gipfl/icingaweb2/src/Zf1/SimpleViewRenderer.php
@@ -0,0 +1,115 @@
+<?php
+
+namespace gipfl\IcingaWeb2\Zf1;
+
+use gipfl\IcingaWeb2\Widget\Content;
+use gipfl\IcingaWeb2\Widget\Controls;
+use ipl\Html\Error;
+use Icinga\Application\Icinga;
+use ipl\Html\ValidHtml;
+use Zend_Controller_Action_Helper_Abstract as Helper;
+use Zend_Controller_Action_HelperBroker as HelperBroker;
+
+class SimpleViewRenderer extends Helper implements ValidHtml
+{
+ private $disabled = false;
+
+ private $rendered = false;
+
+ /** @var \Zend_View_Interface */
+ public $view;
+
+ public function init()
+ {
+ // Register view with action controller (unless already registered)
+ if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
+ $this->_actionController->view = $this->view;
+ }
+ }
+
+ public function disable($disabled = true)
+ {
+ $this->disabled = $disabled;
+ return $this;
+ }
+
+ public function replaceZendViewRenderer()
+ {
+ /** @var \Zend_Controller_Action_Helper_ViewRenderer $viewRenderer */
+ $viewRenderer = Icinga::app()->getViewRenderer();
+ $viewRenderer->setNeverRender();
+ $viewRenderer->setNeverController();
+ HelperBroker::removeHelper('viewRenderer');
+ HelperBroker::addHelper($this);
+ $this->view = $viewRenderer->view;
+ return $this;
+ }
+
+ public function render($action = null, $name = null, $noController = null)
+ {
+ if (null === $name) {
+ $name = null; // $this->getResponseSegment();
+ }
+ // Compat.
+ if (isset($this->_actionController)
+ && get_class($this->_actionController) === 'Icinga\\Controllers\\ErrorController'
+ ) {
+ $html = $this->simulateErrorController();
+ } else {
+ $html = '';
+ if (null !== $this->view->controls) {
+ $html .= $this->view->controls->__toString();
+ }
+
+ if (null !== $this->view->content) {
+ $html .= $this->view->content->__toString();
+ }
+ }
+
+ $this->getResponse()->appendBody($html, $name);
+ // $this->setNoRender();
+ $this->rendered = true;
+ }
+
+ protected function simulateErrorController()
+ {
+ $errorHandler = $this->_actionController->getParam('error_handler');
+ if (isset($errorHandler->exception)) {
+ $error = Error::show($errorHandler->exception);
+ } else {
+ $error = 'An unknown error occured';
+ }
+
+ /** @var \Icinga\Web\Request $request */
+ $request = $this->getRequest();
+ $controls = new Controls();
+ $controls->getTabs()->add('error', [
+ 'label' => t('Error'),
+ 'url' => $request->getUrl(),
+ ])->activate('error');
+ $content = new Content();
+ $content->add($error);
+
+ return $controls . $content;
+ }
+
+ public function shouldRender()
+ {
+ return ! $this->disabled && ! $this->rendered;
+ }
+
+ public function postDispatch()
+ {
+ if ($this->shouldRender()) {
+ $this->render();
+ }
+ }
+
+ public function getName()
+ {
+ // TODO: This is wrong, should be 'viewRenderer' - but that would
+ // currently break nearly everything, starting with full layout
+ // rendering
+ return 'ViewRenderer';
+ }
+}
diff --git a/vendor/gipfl/influxdb/LICENSE b/vendor/gipfl/influxdb/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/influxdb/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/influxdb/composer.json b/vendor/gipfl/influxdb/composer.json
new file mode 100644
index 0000000..8c9aec4
--- /dev/null
+++ b/vendor/gipfl/influxdb/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "gipfl/influxdb",
+ "description": "InfluxDB client library",
+ "type": "library",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "gipfl\\InfluxDb\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "require": {
+ "php": ">=5.6.0",
+ "ext-ctype": "*",
+ "ext-pcntl": "*",
+ "gipfl/curl": ">=0.1.1",
+ "react/event-loop": ">=1.1",
+ "gipfl/json": ">=0.2"
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/ChunkedInfluxDbWriter.php b/vendor/gipfl/influxdb/src/ChunkedInfluxDbWriter.php
new file mode 100644
index 0000000..37473a7
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/ChunkedInfluxDbWriter.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use gipfl\Curl\RequestError;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+
+/**
+ * Gives no result, enqueue and forget
+ */
+class ChunkedInfluxDbWriter implements LoggerAwareInterface
+{
+ use LoggerAwareTrait;
+
+ const DEFAULT_BUFFER_SIZE = 5000;
+
+ const DEFAULT_FLUSH_INTERVAL = 0.2;
+
+ const DEFAULT_PRECISION = 's';
+
+ /** @var int */
+ protected $bufferSize = self::DEFAULT_BUFFER_SIZE;
+
+ /** @var float */
+ protected $flushInterval = self::DEFAULT_FLUSH_INTERVAL;
+
+ /** @var string */
+ protected $precision = self::DEFAULT_PRECISION;
+
+ /** @var DataPoint[] */
+ protected $buffer = [];
+
+ /** @var InfluxDbConnection */
+ protected $connection;
+
+ /** @var string */
+ protected $dbName;
+
+ /** @var LoopInterface */
+ protected $loop;
+
+ /** @var ?TimerInterface */
+ protected $flushTimer;
+
+ public function __construct(InfluxDbConnection $connection, $dbName, LoopInterface $loop)
+ {
+ $this->setLogger(new NullLogger());
+ $this->connection = $connection;
+ $this->dbName = $dbName;
+ $this->loop = $loop;
+ }
+
+ /**
+ * @param DataPoint $point
+ */
+ public function enqueue(DataPoint $point)
+ {
+ $this->buffer[] = $point;
+ $count = count($this->buffer);
+ if ($count >= $this->bufferSize) {
+ $this->flush();
+ } else {
+ $this->startFlushTimer();
+ }
+ }
+
+ /**
+ * @param int $bufferSize
+ * @return ChunkedInfluxDbWriter
+ */
+ public function setBufferSize($bufferSize)
+ {
+ $this->bufferSize = $bufferSize;
+ return $this;
+ }
+
+ /**
+ * @param float $flushInterval
+ * @return ChunkedInfluxDbWriter
+ */
+ public function setFlushInterval($flushInterval)
+ {
+ $this->flushInterval = $flushInterval;
+ return $this;
+ }
+
+ /**
+ * @param string $precision ns,u,ms,s,m,h
+ * @return ChunkedInfluxDbWriter
+ */
+ public function setPrecision($precision)
+ {
+ $this->precision = $precision;
+ return $this;
+ }
+
+ public function flush()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = [];
+ $this->stopFlushTimer();
+ $this->logger->debug(sprintf('Flushing InfluxDB buffer, sending %d data points', count($buffer)));
+ $start = microtime(true);
+ $this->connection->writeDataPoints($this->dbName, $buffer, $this->precision)
+ ->then(function (ResponseInterface $response) use ($start) {
+ $code = $response->getStatusCode();
+ $duration = (microtime(true) - $start) * 1000;
+ if ($code > 199 && $code < 300) {
+ $this->logger->debug(sprintf('Got response from InfluxDB after %.2Fms', $duration));
+ } else {
+ $this->logger->error(sprintf(
+ 'Got unexpected %d from InfluxDB after %.2Fms: %s',
+ $code,
+ $duration,
+ $response->getReasonPhrase()
+ ));
+ }
+ }, function (RequestError $e) {
+ $this->logger->error($e->getMessage());
+ })->done();
+ }
+
+ public function stop()
+ {
+ $this->flush();
+ }
+
+ protected function startFlushTimer()
+ {
+ if ($this->flushTimer === null) {
+ $this->flushTimer = $this->loop->addPeriodicTimer($this->flushInterval, function () {
+ if (! empty($this->buffer)) {
+ $this->flush();
+ }
+ });
+ }
+ }
+
+ protected function stopFlushTimer()
+ {
+ if ($this->flushTimer) {
+ $this->loop->cancelTimer($this->flushTimer);
+ $this->flushTimer = null;
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->stopFlushTimer();
+ $this->loop = null;
+ $this->connection = null;
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/DataPoint.php b/vendor/gipfl/influxdb/src/DataPoint.php
new file mode 100644
index 0000000..f272206
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/DataPoint.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use InvalidArgumentException;
+use function array_key_exists;
+use function array_merge;
+use function is_array;
+use function is_object;
+use function ksort;
+
+class DataPoint
+{
+ protected $timestamp;
+
+ protected $measurement;
+
+ protected $tags = [];
+
+ protected $fields;
+
+ public function __construct($measurement, $tags = [], $fields = [], $timestamp = null)
+ {
+ $this->measurement = (string) $measurement;
+ if ($timestamp !== null) {
+ $this->timestamp = $timestamp;
+ }
+
+ if (! empty($tags)) {
+ $this->addTags($tags);
+ }
+
+ if (is_array($fields) || is_object($fields)) {
+ $this->fields = (array) $fields;
+ } else {
+ $this->fields = ['value' => $fields];
+ }
+
+ if (empty($this->fields)) {
+ throw new InvalidArgumentException('At least one field/value is required');
+ }
+ }
+
+ public function addTags($tags)
+ {
+ $this->tags = array_merge($this->tags, (array) $tags);
+ ksort($this->tags);
+ }
+
+ public function getTag($name, $default = null)
+ {
+ if (array_key_exists($name, $this->tags)) {
+ return $this->tags[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ public function __toString()
+ {
+ return LineProtocol::renderMeasurement($this->measurement, $this->tags, $this->fields, $this->timestamp);
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/Escape.php b/vendor/gipfl/influxdb/src/Escape.php
new file mode 100644
index 0000000..e6cb555
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/Escape.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use InvalidArgumentException;
+use function addcslashes;
+use function ctype_digit;
+use function is_bool;
+use function is_int;
+use function is_null;
+use function preg_match;
+use function strpos;
+
+abstract class Escape
+{
+ const ESCAPE_COMMA_SPACE = ' ,\\';
+
+ const ESCAPE_COMMA_EQUAL_SPACE = ' =,\\';
+
+ const ESCAPE_DOUBLE_QUOTES = '"\\';
+
+ const NULL = 'null';
+
+ const TRUE = 'true';
+
+ const FALSE = 'false';
+
+ public static function measurement($value)
+ {
+ static::assertNoNewline($value);
+ return addcslashes($value, self::ESCAPE_COMMA_SPACE);
+ }
+
+ public static function key($value)
+ {
+ static::assertNoNewline($value);
+ return addcslashes($value, self::ESCAPE_COMMA_EQUAL_SPACE);
+ }
+
+ public static function tagValue($value)
+ {
+ static::assertNoNewline($value);
+ return addcslashes($value, self::ESCAPE_COMMA_EQUAL_SPACE);
+ }
+
+ public static function fieldValue($value)
+ {
+ // Faster checks first
+ if (is_int($value) || ctype_digit($value) || preg_match('/^-\d+$/', $value)) {
+ return "{$value}i";
+ } elseif (is_bool($value)) {
+ return $value ? self::TRUE : self::FALSE;
+ } elseif (is_null($value)) {
+ return self::NULL;
+ } else {
+ static::assertNoNewline($value);
+ return '"' . addcslashes($value, self::ESCAPE_DOUBLE_QUOTES) . '"';
+ }
+ }
+
+ protected static function assertNoNewline($value)
+ {
+ if (strpos($value, "\n") !== false) {
+ throw new InvalidArgumentException('Newlines are forbidden in InfluxDB line protocol');
+ }
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/InfluxDbConnection.php b/vendor/gipfl/influxdb/src/InfluxDbConnection.php
new file mode 100644
index 0000000..d20944a
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/InfluxDbConnection.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+interface InfluxDbConnection
+{
+ public function ping($verbose = false);
+
+ public function getVersion();
+
+ public function listDatabases();
+
+ public function createDatabase($name);
+
+ public function getHealth();
+
+ /**
+ * @param string $dbName
+ * @param DataPoint[] $dataPoints
+ * @param string|null $precision ns,u,ms,s,m,h
+ * @return \React\Promise\Promise
+ */
+ public function writeDataPoints($dbName, array $dataPoints, $precision = null);
+}
diff --git a/vendor/gipfl/influxdb/src/InfluxDbConnectionFactory.php b/vendor/gipfl/influxdb/src/InfluxDbConnectionFactory.php
new file mode 100644
index 0000000..f260010
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/InfluxDbConnectionFactory.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use gipfl\Curl\CurlAsync;
+use React\EventLoop\LoopInterface;
+use React\Promise\Promise;
+use RuntimeException;
+
+abstract class InfluxDbConnectionFactory
+{
+ /**
+ * AsyncInfluxDbWriter constructor.
+ * @param LoopInterface $loop
+ * @param $baseUrl string InfluxDB base URL
+ * @param string|null $username
+ * @param string|null $password
+ * @return Promise <InfluxDbConnection>
+ */
+ public static function create(CurlAsync $curl, $baseUrl, $username = null, $password = null)
+ {
+ $v1 = new InfluxDbConnectionV1($curl, $baseUrl);
+ return $v1->getVersion()->then(function ($version) use ($baseUrl, $username, $password, $curl, $v1) {
+ if ($version === null || preg_match('/^v?2\./', $version)) {
+ $v2 = new InfluxDbConnectionV2($curl, $baseUrl, $username, $password);
+ return $v2->getVersion()->then(function ($version) use ($v2) {
+ if ($version === null) {
+ throw new RuntimeException('Unable to detect InfluxDb version');
+ } else {
+ return $v2;
+ }
+ });
+ } else {
+ return $v1;
+ }
+ });
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/InfluxDbConnectionV1.php b/vendor/gipfl/influxdb/src/InfluxDbConnectionV1.php
new file mode 100644
index 0000000..0b674c2
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/InfluxDbConnectionV1.php
@@ -0,0 +1,311 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use gipfl\Curl\CurlAsync;
+use gipfl\Json\JsonString;
+use Psr\Http\Message\ResponseInterface;
+use Ramsey\Uuid\Uuid;
+use React\Promise\Promise;
+use function React\Promise\resolve;
+
+class InfluxDbConnectionV1 implements InfluxDbConnection
+{
+ const API_VERSION = 'v1';
+
+ const USER_AGENT = 'gipfl-InfluxDB/0.5';
+
+ /** @var string */
+ protected $baseUrl;
+
+ protected $version;
+
+ /** @var string|null */
+ protected $username;
+
+ /** @var string|null */
+ protected $password;
+
+ protected $curl;
+
+ /**
+ * AsyncInfluxDbWriter constructor.
+ * @param CurlAsync $curl
+ * @param string $baseUrl InfluxDB base URL
+ * @param ?string $username
+ * @param ?string $password
+ */
+ public function __construct(CurlAsync $curl, $baseUrl, $username = null, $password = null)
+ {
+ $this->baseUrl = rtrim($baseUrl, '/');
+ $this->curl = $curl;
+ $this->setUsername($username);
+ $this->setPassword($password);
+ }
+
+ /**
+ * @param string|null $username
+ * @return $this
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+ return $this;
+ }
+
+ /**
+ * @param string|null $password
+ * @return $this
+ */
+ public function setPassword($password)
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ public function ping($verbose = false)
+ {
+ $params = [];
+ if ($verbose) {
+ $params['verbose'] = 'true';
+ }
+ return $this->getUrl('ping', $params);
+ }
+
+ public function getVersion()
+ {
+ if ($this->version) {
+ return resolve($this->version);
+ }
+
+ return $this->get('ping')->then(function (ResponseInterface $response) {
+ foreach ($response->getHeader('X-Influxdb-Version') as $version) {
+ return $this->version = $version;
+ }
+
+ return null;
+ });
+ }
+
+ public function listDatabases()
+ {
+ return $this->query('SHOW DATABASES')->then(function ($result) {
+ return InfluxDbQueryResult::extractColumn($result);
+ });
+ }
+
+ public function createDatabase($name)
+ {
+ return $this->query('CREATE DATABASE ' . Escape::fieldValue($name))->then(function ($result) {
+ return $result;
+ });
+ }
+
+ /**
+ * only since vX
+ */
+ public function getHealth()
+ {
+ // Works without Auth
+ return $this->getUrl('health');
+ }
+
+ protected function query($query)
+ {
+ if (is_array($query)) {
+ $sendQueries = \array_values($query);
+ } else {
+ $sendQueries = [$query];
+ }
+ if (empty($query)) {
+ throw new \InvalidArgumentException('Cannot run no query');
+ }
+
+ if (preg_match('/^(SELECT|SHOW|ALTER|CREATE|DELETE|DROP|GRANT|KILL|REVOKE) /', $sendQueries[0], $match)) {
+ $queryType = $match[1];
+ } else {
+ throw new \InvalidArgumentException('Unable to detect query type: ' . $sendQueries[0]);
+ }
+ if ($queryType === 'SHOW') {
+ $queryType = 'GET';
+ } elseif ($queryType === 'SELECT') {
+ if (strpos($sendQueries[0], ' INTO ') === false) {
+ $queryType = 'POST';
+ } else {
+ $queryType = 'GET';
+ }
+ } else {
+ $queryType = 'POST';
+ }
+ $prefix = '';
+
+ // TODO: Temporarily disabled, had problems with POST params in the body
+ if ($queryType === 'xPOST') {
+ $headers = ['Content-Type' => 'x-www-form-urlencoded'];
+ $body = \http_build_query(['q' => implode(';', $sendQueries)]);
+ $urlParams = [];
+ $promise = $this->curl->post(
+ $this->url("{$prefix}query", $urlParams),
+ $this->getRequestHeaders() + $headers,
+ $body
+ );
+ } else {
+ $urlParams = ['q' => implode(';', $sendQueries)];
+ $promise = $this->curl->get(
+ $this->url("{$prefix}query", $urlParams),
+ $this->getRequestHeaders()
+ );
+ }
+
+ /** @var Promise $promise */
+ return $promise->then(function (ResponseInterface $response) use ($sendQueries, $query) {
+ $body = $response->getBody();
+ if (! ($response->getStatusCode() < 300)) {
+ throw new \Exception($response->getReasonPhrase());
+ }
+ if (preg_match('#^application/json#', \current($response->getHeader('content-type')))) {
+ $decoded = JsonString::decode((string) $body);
+ } else {
+ throw new \RuntimeException(\sprintf(
+ 'JSON response expected, got %s: %s',
+ current($response->getHeader('content-type')),
+ $body
+ ));
+ }
+ $results = [];
+ foreach ($decoded->results as $result) {
+ if (isset($result->series)) {
+ $results[$result->statement_id] = $result->series[0];
+ } elseif (isset($result->error)) {
+ $results[$result->statement_id] = new \Exception('InfluxDB error: ' . $result->error);
+ } else {
+ $results[$result->statement_id] = null;
+ }
+ }
+ if (\count($results) !== \count($sendQueries)) {
+ throw new \InvalidArgumentException(\sprintf(
+ 'Sent %d statements, but got %d results',
+ \count($sendQueries),
+ \count($results)
+ ));
+ }
+
+ if (is_array($query)) {
+ return \array_combine(\array_keys($query), $results);
+ } else {
+ return $results[0];
+ }
+ });
+ }
+
+ /**
+ * @param string $dbName
+ * @param DataPoint[] $dataPoints
+ * @param string|null $precision ns,u,ms,s,m,h
+ * @return \React\Promise\Promise
+ */
+ public function writeDataPoints($dbName, array $dataPoints, $precision = null)
+ {
+ $body = gzencode(\implode($dataPoints), 6);
+ $params = ['db' => $dbName];
+ if ($precision !== null) {
+ $params['precision'] = $precision;
+ }
+ $headers = [
+ 'X-Request-Id' => Uuid::uuid4()->toString(),
+ 'Content-Encoding' => 'gzip',
+ 'Content-Length' => strlen($body),
+ ];
+ // params['rp'] = $retentionPolicy
+ /** @var Promise $promise */
+ return $this->curl->post(
+ $this->url('write', $params),
+ $this->getRequestHeaders() + $headers,
+ $body,
+ $this->getDefaultCurlOptions()
+ );
+ }
+
+ protected function getDefaultCurlOptions()
+ {
+ return [
+ // Hint: avoid 100/Continue
+ CURLOPT_HTTPHEADER => [
+ 'Expect:',
+ ]
+ ];
+ }
+
+ protected function getRequestHeaders()
+ {
+ $headers = [
+ 'User-Agent' => static::USER_AGENT,
+ ];
+ if ($this->username !== null) {
+ $headers['Authorization'] = 'Basic '
+ . \base64_encode($this->username . ':' . $this->password);
+ }
+
+ return $headers;
+ }
+
+ protected function get($url, $params = null)
+ {
+ return $this->curl->get(
+ $this->url($url, $params),
+ $this->getRequestHeaders()
+ );
+ }
+
+ protected function getRaw($url, $params = null)
+ {
+ /** @var Promise $promise */
+ $promise = $this
+ ->get($url, $params)
+ ->then(function (ResponseInterface $response) {
+ return (string) $response->getBody();
+ });
+
+ return $promise;
+ }
+
+ protected function postRaw($url, $body, $headers = [], $urlParams = [])
+ {
+ /** @var Promise $promise */
+ $promise = $this->curl->post(
+ $this->url($url, $urlParams),
+ $this->getRequestHeaders() + $headers + [
+ 'Content-Type' => 'application/json'
+ ],
+ $body
+ )->then(function (ResponseInterface $response) {
+ return (string) $response->getBody();
+ });
+
+ return $promise;
+ }
+
+ protected function getUrl($url, $params = null)
+ {
+ return $this->getRaw($url, $params)->then(function ($raw) {
+ return JsonString::decode((string) $raw);
+ });
+ }
+
+ protected function postUrl($url, $body, $headers = [], $urlParams = [])
+ {
+ return $this->postRaw($url, JsonString::encode($body), $headers, $urlParams)->then(function ($raw) {
+ return JsonString::decode((string) $raw);
+ });
+ }
+
+ protected function url($path, $params = [])
+ {
+ $url = $this->baseUrl . "/$path";
+ if (! empty($params)) {
+ $url .= '?' . \http_build_query($params);
+ }
+
+ return $url;
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/InfluxDbConnectionV2.php b/vendor/gipfl/influxdb/src/InfluxDbConnectionV2.php
new file mode 100644
index 0000000..d244786
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/InfluxDbConnectionV2.php
@@ -0,0 +1,270 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use gipfl\Curl\CurlAsync;
+use gipfl\Json\JsonString;
+use Psr\Http\Message\ResponseInterface;
+use Ramsey\Uuid\Uuid;
+use React\EventLoop\LoopInterface;
+use React\Promise\Promise;
+
+class InfluxDbConnectionV2 implements InfluxDbConnection
+{
+ const API_VERSION = 'v2';
+
+ const USER_AGENT = 'gipfl-InfluxDB/0.5';
+
+ /** @var CurlAsync */
+ protected $curl;
+
+ /** @var string */
+ protected $baseUrl;
+
+ /** @var string|null */
+ protected $token;
+
+ /** @var string|null */
+ protected $org;
+
+ /**
+ * AsyncInfluxDbWriter constructor.
+ * @param $baseUrl string InfluxDB base URL
+ * @param LoopInterface $loop
+ */
+ public function __construct(CurlAsync $curl, $baseUrl, $org, $token)
+ {
+ $this->baseUrl = $baseUrl;
+ $this->setOrg($org);
+ $this->setToken($token);
+ $this->curl = $curl;
+ }
+
+ /**
+ * @param string|null $token
+ * @return $this
+ */
+ public function setToken($token)
+ {
+ $this->token = $token;
+
+ return $this;
+ }
+
+ /**
+ * @param string|null $org
+ * @return $this
+ */
+ public function setOrg($org)
+ {
+ $this->org = $org;
+
+ return $this;
+ }
+
+ public function ping($verbose = false)
+ {
+ $params = [];
+ if ($verbose) {
+ $params['verbose'] = 'true';
+ }
+ return $this->getUrl('ping', $params);
+ }
+
+ public function getVersion()
+ {
+ return $this->getHealth()->then(function ($result) {
+ return $result->version;
+ });
+ }
+
+ public function getMyOrgId()
+ {
+ return $this->getUrl('api/v2/orgs', ['org' => urlencode($this->org)])->then(function ($result) {
+ foreach ($result->orgs as $org) {
+ if ($org->name === $this->org) {
+ return $org->id;
+ }
+ }
+
+ throw new \RuntimeException('Org "' . $this->org . '" not found');
+ });
+ }
+
+ public function listDatabases()
+ {
+ // ->links->self = "/api/v2/buckets?descending=false\u0026limit=2\u0026offset=0"
+ // ->links->next = "next": "/api/v2/buckets?descending=false\u0026limit=2\u0026offset=2"
+ // 100 -> maxlimit
+ return $this->getUrl('api/v2/buckets', ['limit' => 100])->then(function ($result) {
+ $list = [];
+ foreach ($result->buckets as $bucket) {
+ $list[] = $bucket->name;
+ }
+
+ return $list;
+ });
+ }
+
+ public function createDatabase($name)
+ {
+ return $this->getMyOrgId()->then(function ($orgId) use ($name) {
+ return $this->postUrl('api/v2/buckets', [
+ 'orgID' => $orgId,
+ 'name' => $name,
+ 'retentionRules' => [(object) [
+ 'type' => 'expire',
+ 'everySeconds' => 86400 * 7,
+ ]]
+ ]);
+ })->then(function ($result) {
+ return $result;
+ });
+ }
+
+ public function getHealth()
+ {
+ // Works without Auth
+ return $this->getUrl('health');
+ }
+
+ /**
+ * TODO: unfinished
+ * @param $query
+ * @throws \gipfl\Json\JsonEncodeException
+ */
+ protected function query($query)
+ {
+ $prefix = "api/v2/";
+ $headers = ['Content-Type' => 'application/json'];
+ $body = JsonString::encode(['query' => $query]);
+ $urlParams = ['org' => $this->org];
+ }
+
+ /**
+ * @param string $dbName
+ * @param DataPoint[] $dataPoints
+ * @param string|null $precision ns,u,ms,s,m,h
+ * @return \React\Promise\Promise
+ */
+ public function writeDataPoints($dbName, array $dataPoints, $precision = null)
+ {
+ $body = gzencode(\implode($dataPoints), 6);
+ $params = [
+ 'org' => $this->org,
+ 'bucket' => $dbName,
+ // TODO: Figure out, whether 2.0.0 also supports bucket. If so, drop db
+ 'db' => $dbName,
+ ];
+ $headers = [
+ 'X-Request-Id' => Uuid::uuid4()->toString(),
+ 'Content-Encoding' => 'gzip',
+ 'Content-Length' => strlen($body),
+ ];
+ if ($precision !== null) {
+ $params['precision'] = $precision;
+ }
+ // params['rp'] = $retentionPolicy
+ return $this->curl->post(
+ $this->url('api/v2/write', $params),
+ $this->defaultHeaders() + $headers,
+ $body,
+ $this->getDefaultCurlOptions()
+ );
+ }
+
+ protected function getDefaultCurlOptions()
+ {
+ return [
+ // Hint: avoid 100/Continue
+ CURLOPT_HTTPHEADER => [
+ 'Expect:',
+ ]
+ ];
+ }
+
+ protected function defaultHeaders()
+ {
+ $headers = [
+ 'User-Agent' => static::USER_AGENT,
+ ];
+ if ($this->token) {
+ $headers['Authorization'] = 'Token ' . $this->token;
+ }
+
+ return $headers;
+ }
+
+ protected function get($url, $params = null)
+ {
+ return $this->curl->get(
+ $this->url($url, $params),
+ $this->defaultHeaders()
+ );
+ }
+
+ protected function getRaw($url, $params = null)
+ {
+ /** @var Promise $promise */
+ $promise = $this
+ ->get($url, $params)
+ ->then(function (ResponseInterface $response) {
+ if ($response->getStatusCode() < 300) {
+ return (string) $response->getBody();
+ } else {
+ try {
+ $body = JsonString::decode($response->getBody());
+ } catch (\Exception $e) {
+ throw new \Exception($response->getReasonPhrase());
+ }
+ if (isset($body->message)) {
+ throw new \Exception($body->message);
+ } else {
+ throw new \Exception($response->getReasonPhrase());
+ }
+ }
+ });
+
+ return $promise;
+ }
+
+ protected function postRaw($url, $body, $headers = [], $urlParams = [])
+ {
+ /** @var Promise $promise */
+ $promise = $this->curl->post(
+ $this->url($url, $urlParams),
+ $this->defaultHeaders() + $headers + [
+ 'Content-Type' => 'application/json'
+ ],
+ $body
+ )->then(function (ResponseInterface $response) {
+ return (string) $response->getBody();
+ });
+
+ return $promise;
+ }
+
+ protected function getUrl($url, $params = null)
+ {
+ return $this->getRaw($url, $params)->then(function ($raw) {
+ return JsonString::decode((string) $raw);
+ });
+ }
+
+ protected function postUrl($url, $body, $headers = [], $urlParams = [])
+ {
+ return $this->postRaw($url, JsonString::encode($body), $headers, $urlParams)->then(function ($raw) {
+ return JsonString::decode((string) $raw);
+ });
+ }
+
+ protected function url($path, $params = [])
+ {
+ $url = $this->baseUrl . "/$path";
+ if (! empty($params)) {
+ $url .= '?' . \http_build_query($params);
+ }
+
+ return $url;
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/InfluxDbQueryResult.php b/vendor/gipfl/influxdb/src/InfluxDbQueryResult.php
new file mode 100644
index 0000000..0ca6fd1
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/InfluxDbQueryResult.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use InvalidArgumentException;
+
+class InfluxDbQueryResult
+{
+ public static function extractColumn($result, $idx = 0)
+ {
+ if (! isset($result->columns)) {
+ print_r($result);
+ exit;
+ }
+ $idx = static::getNumericColumn($idx, $result->columns);
+ $column = [];
+ foreach ($result->values as $row) {
+ $column[] = $row[$idx];
+ }
+
+ return $column;
+ }
+
+ protected static function getNumericColumn($name, $cols)
+ {
+ if (\is_int($name)) {
+ if (isset($cols[$name])) {
+ return $name;
+ }
+ }
+ if (\is_string($name)) {
+ foreach ($cols as $idx => $alias) {
+ if ($name === $alias) {
+ return $idx;
+ }
+ }
+ }
+
+ throw new InvalidArgumentException("There is no '$name' column in the result");
+ }
+
+ protected static function extractPairs($result, $keyColumn = 0, $valueColumn = 1)
+ {
+ $keyColumn = static::getNumericColumn($keyColumn, $result->columns);
+ $valueColumn = static::getNumericColumn($valueColumn, $result->columns);
+ $pairs = [];
+ foreach ($result->values as $row) {
+ $pairs[$row[$keyColumn]] = $row[$valueColumn];
+ }
+
+ return $pairs;
+ }
+
+ protected static function transformResultsTable($table)
+ {
+ // $table->name = 'databases'
+ $cols = $table->columns;
+ $values = [];
+ foreach ($table->values as $row) {
+ $values[] = (object) \array_combine($cols, $row);
+ }
+
+ return $values;
+ }
+}
diff --git a/vendor/gipfl/influxdb/src/LineProtocol.php b/vendor/gipfl/influxdb/src/LineProtocol.php
new file mode 100644
index 0000000..b3b5f4a
--- /dev/null
+++ b/vendor/gipfl/influxdb/src/LineProtocol.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace gipfl\InfluxDb;
+
+use function ksort;
+use function strlen;
+
+abstract class LineProtocol
+{
+ public static function renderMeasurement($measurement, $tags = [], $fields = [], $timestamp = null)
+ {
+ return Escape::measurement($measurement)
+ . static::renderTags($tags)
+ . static::renderFields($fields)
+ . static::renderTimeStamp($timestamp)
+ . "\n";
+ }
+
+ public static function renderTags($tags)
+ {
+ ksort($tags);
+ $string = '';
+ foreach ($tags as $key => $value) {
+ if ($value === null || strlen($value) === 0) {
+ continue;
+ }
+ $string .= ',' . static::renderTag($key, $value);
+ }
+
+ return $string;
+ }
+
+ public static function renderFields($fields)
+ {
+ $string = '';
+ foreach ($fields as $key => $value) {
+ $string .= ',' . static::renderField($key, $value);
+ }
+ $string[0] = ' ';
+
+ return $string;
+ }
+
+ public static function renderTimeStamp($timestamp)
+ {
+ if ($timestamp === null) {
+ return '';
+ } else {
+ return " $timestamp";
+ }
+ }
+
+ public static function renderTag($key, $value)
+ {
+ return Escape::key($key) . '=' . Escape::tagValue($value);
+ }
+
+ public static function renderField($key, $value)
+ {
+
+ return Escape::key($key) . '=' . Escape::fieldValue($value);
+ }
+}
diff --git a/vendor/gipfl/json/composer.json b/vendor/gipfl/json/composer.json
new file mode 100644
index 0000000..7e3f93c
--- /dev/null
+++ b/vendor/gipfl/json/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "gipfl/json",
+ "description": "Simple JSON-related helper classes and interfaces",
+ "type": "library",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Json\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "require": {
+ "ext-json": "*"
+ }
+}
diff --git a/vendor/gipfl/json/src/JsonDecodeException.php b/vendor/gipfl/json/src/JsonDecodeException.php
new file mode 100644
index 0000000..cd19aa7
--- /dev/null
+++ b/vendor/gipfl/json/src/JsonDecodeException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace gipfl\Json;
+
+class JsonDecodeException extends JsonException
+{
+}
diff --git a/vendor/gipfl/json/src/JsonEncodeException.php b/vendor/gipfl/json/src/JsonEncodeException.php
new file mode 100644
index 0000000..e9fcc5f
--- /dev/null
+++ b/vendor/gipfl/json/src/JsonEncodeException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace gipfl\Json;
+
+class JsonEncodeException extends JsonException
+{
+}
diff --git a/vendor/gipfl/json/src/JsonException.php b/vendor/gipfl/json/src/JsonException.php
new file mode 100644
index 0000000..e7b5f36
--- /dev/null
+++ b/vendor/gipfl/json/src/JsonException.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace gipfl\Json;
+
+use Exception;
+
+class JsonException extends Exception
+{
+ public static function forLastJsonError($msg = null)
+ {
+ if ($msg === null) {
+ return new static(static::getJsonErrorMessage(\json_last_error()));
+ } else {
+ return new static($msg . ': ' . static::getJsonErrorMessage(\json_last_error()));
+ }
+ }
+
+ public static function getJsonErrorMessage($code)
+ {
+ $map = [
+ JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
+ JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
+ JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
+ JSON_ERROR_SYNTAX => 'JSON Syntax error',
+ JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
+ ];
+ if (\array_key_exists($code, $map)) {
+ return $map[$code];
+ }
+
+ if (PHP_VERSION_ID >= 50500) {
+ $map = [
+ JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded',
+ JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded',
+ JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given',
+ ];
+ if (\array_key_exists($code, $map)) {
+ return $map[$code];
+ }
+ }
+
+ if (PHP_VERSION_ID >= 70000) {
+ $map = [
+ JSON_ERROR_INVALID_PROPERTY_NAME => 'A property name that cannot be encoded was given',
+ JSON_ERROR_UTF16 => 'Malformed UTF-16 characters, possibly incorrectly encoded',
+ ];
+
+ if (\array_key_exists($code, $map)) {
+ return $map[$code];
+ }
+ }
+
+ return 'An error occured when parsing a JSON string';
+ }
+}
diff --git a/vendor/gipfl/json/src/JsonSerialization.php b/vendor/gipfl/json/src/JsonSerialization.php
new file mode 100644
index 0000000..f5b058e
--- /dev/null
+++ b/vendor/gipfl/json/src/JsonSerialization.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace gipfl\Json;
+
+use JsonSerializable;
+
+interface JsonSerialization extends JsonSerializable
+{
+ /**
+ * @param mixed $any
+ * @return static
+ */
+ public static function fromSerialization($any);
+}
diff --git a/vendor/gipfl/json/src/JsonString.php b/vendor/gipfl/json/src/JsonString.php
new file mode 100644
index 0000000..b9e22b6
--- /dev/null
+++ b/vendor/gipfl/json/src/JsonString.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace gipfl\Json;
+
+use function json_decode;
+use function json_encode;
+use function json_last_error;
+
+class JsonString
+{
+ const DEFAULT_FLAGS = JSON_PRESERVE_ZERO_FRACTION | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+
+ /**
+ * Encode with well-known flags, as we require the result to be reproducible
+ *
+ * @param $mixed
+ * @param int|null $flags
+ * @return string
+ * @throws JsonEncodeException
+ */
+ public static function encode($mixed, $flags = null)
+ {
+ if ($flags === null) {
+ $flags = self::DEFAULT_FLAGS;
+ } else {
+ $flags = self::DEFAULT_FLAGS | $flags;
+ }
+ $result = json_encode($mixed, $flags);
+
+ if ($result === false && json_last_error() !== JSON_ERROR_NONE) {
+ throw JsonEncodeException::forLastJsonError();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Decode the given JSON string and make sure we get a meaningful Exception
+ *
+ * @param string $string
+ * @return mixed
+ * @throws JsonDecodeException
+ */
+ public static function decode($string)
+ {
+ $result = json_decode($string);
+
+ if ($result === null && json_last_error() !== JSON_ERROR_NONE) {
+ throw JsonDecodeException::forLastJsonError();
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param $string
+ * @return ?string
+ * @throws JsonDecodeException
+ */
+ public static function decodeOptional($string)
+ {
+ if ($string === null) {
+ return null;
+ }
+
+ return static::decode($string);
+ }
+}
diff --git a/vendor/gipfl/json/src/SerializationHelper.php b/vendor/gipfl/json/src/SerializationHelper.php
new file mode 100644
index 0000000..0714e30
--- /dev/null
+++ b/vendor/gipfl/json/src/SerializationHelper.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace gipfl\Json;
+
+use InvalidArgumentException;
+use JsonSerializable;
+use stdClass;
+
+class SerializationHelper
+{
+ /**
+ * TODO: Check whether json_encode() is faster
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ public static function assertSerializableValue($value)
+ {
+ if ($value === null || is_scalar($value)) {
+ return true;
+ }
+ if (is_object($value)) {
+ if ($value instanceof JsonSerializable) {
+ return true;
+ }
+
+ if ($value instanceof stdClass) {
+ foreach ((array) $value as $val) {
+ static::assertSerializableValue($val);
+ }
+
+ return true;
+ }
+ }
+
+ if (is_array($value)) {
+ foreach ($value as $val) {
+ static::assertSerializableValue($val);
+ }
+
+ return true;
+ }
+
+ throw new InvalidArgumentException('Serializable value expected, got ' . static::getPhpType($value));
+ }
+
+ public static function getPhpType($var)
+ {
+ if (is_object($var)) {
+ return get_class($var);
+ }
+
+ return gettype($var);
+ }
+}
diff --git a/vendor/gipfl/linux-health/composer.json b/vendor/gipfl/linux-health/composer.json
new file mode 100644
index 0000000..d162cc8
--- /dev/null
+++ b/vendor/gipfl/linux-health/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "gipfl/linux-health",
+ "description": "Various little Linux Health based classes",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\LinuxHealth\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.4.0"
+ }
+}
diff --git a/vendor/gipfl/linux-health/src/Cpu.php b/vendor/gipfl/linux-health/src/Cpu.php
new file mode 100644
index 0000000..43f2e50
--- /dev/null
+++ b/vendor/gipfl/linux-health/src/Cpu.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace gipfl\LinuxHealth;
+
+class Cpu
+{
+ public static function getCounters($procFile = '/proc/stat')
+ {
+ $info = [];
+ $cpus = [];
+
+ $cpuKeys = [ // From 'man proc':
+ 'user', // Time spent in user mode.
+ 'nice', // Time spent in user mode with low priority (nice).
+ 'system', // Time spent in system mode.
+ 'idle', // Time spent in the idle task.
+ // This value should be USER_HZ times the second entry in the
+ // /proc/uptime pseudo-file.
+ 'iowait', // Time waiting for I/O to complete. (Linux >= 2.5.41)
+ 'irq', // Time servicing interrupts. (Linux >= 2.6.0-test4)
+ 'softirq', // Time servicing softirqs. (Linux >= 2.6.0-test4)
+ 'steal', // Stolen time, which is the time spent in other operating
+ // systems when running in a virtualized environment
+ // (Linux >= 2.6.11)
+ 'guest', // Time spent running a virtual CPU for guest operating systems
+ // under the control of the Linux kernel. (Linux >= 2.6.24)
+ 'guest_nice' // Time spent running a niced guest (virtual CPU for guest
+ // operating systems under the control of the Linux kernel).
+ // (Linux >= 2.6.33)
+ ];
+
+ // TODO:
+ // ctxt 891299797 -> The number of context switches that the system underwent
+ // btime 1540828526 -> boot time, in seconds since the Epoch
+ // processes 2079015 -> Number of forks since boot
+ // procs_running 6 -> Number of processes in runnable state
+ // procs_blocked 0 -> Number of processes blocked waiting for I/O to complete
+
+ foreach (file($procFile, FILE_IGNORE_NEW_LINES) as $line) {
+ $parts = preg_split('/\s+/', $line);
+ $key = array_shift($parts);
+ if (substr($key, 0, 3) === 'cpu') {
+ // TODO: handle count mismatch
+ $cpus[$key] = array_combine(
+ array_slice($cpuKeys, 0, count($parts), true),
+ $parts
+ );
+
+ for ($i = count($cpus[$key]) - 1; $i < count($cpuKeys); $i++) {
+ $cpus[$key][$cpuKeys[$i]] = 0;
+ }
+ } else {
+ $info[$key] = $parts;
+ }
+ }
+
+ return $cpus;
+ }
+}
diff --git a/vendor/gipfl/linux-health/src/Memory.php b/vendor/gipfl/linux-health/src/Memory.php
new file mode 100644
index 0000000..0c6f197
--- /dev/null
+++ b/vendor/gipfl/linux-health/src/Memory.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace gipfl\LinuxHealth;
+
+class Memory
+{
+ protected static $pageSize;
+
+ public static function getUsageForPid($pid)
+ {
+ $pid = (int) $pid;
+ $content = @file_get_contents("/proc/$pid/statm");
+ if ($content === false) {
+ return false;
+ }
+
+ $pageSize = static::getPageSize();
+ if ($pageSize === null) {
+ return false;
+ }
+ $parts = explode(' ', $content);
+
+ return (object) [
+ 'size' => $pageSize * (int) $parts[0],
+ 'rss' => $pageSize * (int) $parts[1],
+ 'shared' => $pageSize * (int) $parts[3],
+ ];
+ }
+
+ /**
+ * @return int
+ */
+ public static function getPageSize()
+ {
+ if (self::$pageSize === null) {
+ $output = trim(`getconf PAGESIZE 2>&1`);
+ if (strlen($output)) {
+ self::$pageSize = (int) $output;
+ }
+ }
+
+ return self::$pageSize;
+ }
+
+ /**
+ * @param int $pageSize
+ */
+ public static function setPageSize($pageSize)
+ {
+ self::$pageSize = (int) $pageSize;
+ }
+}
diff --git a/vendor/gipfl/linux-health/src/Network.php b/vendor/gipfl/linux-health/src/Network.php
new file mode 100644
index 0000000..e0bad7d
--- /dev/null
+++ b/vendor/gipfl/linux-health/src/Network.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace gipfl\LinuxHealth;
+
+class Network
+{
+ public static function getInterfaceCounters($procFile = '/proc/net/dev')
+ {
+ // Header looks like this:
+ // Inter-| Receive | Transmit
+ // face |bytes packets errs drop fifo frame compressed multicast|bytes packets
+ // (...from above line) errs drop fifo colls carrier compressed
+
+ $lines = \file($procFile, FILE_IGNORE_NEW_LINES);
+ \array_shift($lines);
+ $headers = preg_split('/\|/', array_shift($lines));
+ $rxHeaders = preg_split('/\s+/', $headers[1]);
+ $txHeaders = preg_split('/\s+/', $headers[2]);
+
+ $headers = [];
+ foreach ($rxHeaders as $rx) {
+ $headers[] = 'rx' . ucfirst($rx);
+ }
+ foreach ($txHeaders as $tx) {
+ $headers[] = 'tx' . ucfirst($tx);
+ }
+ $interfaces = [];
+ foreach ($lines as $line) {
+ $parts = preg_split('/\s+|\|/', trim($line));
+ $ifName = rtrim(array_shift($parts), ':');
+ $interfaces[$ifName] = (object) array_combine($headers, $parts);
+ }
+
+ return $interfaces;
+ }
+}
diff --git a/vendor/gipfl/log/LICENSE b/vendor/gipfl/log/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/log/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/log/composer.json b/vendor/gipfl/log/composer.json
new file mode 100644
index 0000000..c300158
--- /dev/null
+++ b/vendor/gipfl/log/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "gipfl/log",
+ "description": "Lightweight PSR-3 compatible logger",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Log\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "psr/log": "^1",
+ "ext-iconv": "*"
+ },
+ "require-dev": {
+ "gipfl/protocol-jsonrpc": ">=0.2",
+ "gipfl/systemd": ">=0.3"
+ },
+ "suggest": {
+ }
+}
diff --git a/vendor/gipfl/log/src/AdditionalContextLogger.php b/vendor/gipfl/log/src/AdditionalContextLogger.php
new file mode 100644
index 0000000..92891c5
--- /dev/null
+++ b/vendor/gipfl/log/src/AdditionalContextLogger.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace gipfl\Log;
+
+use Psr\Log\LoggerInterface;
+
+class AdditionalContextLogger extends Logger
+{
+ /** @var array */
+ protected $context;
+
+ /** @var LoggerInterface */
+ protected $wrappedLogger;
+
+ public function __construct(array $context, LoggerInterface $logger)
+ {
+ $this->context = $context;
+ $this->wrappedLogger = $logger;
+ }
+
+ public function log($level, $message, array $context = [])
+ {
+ $this->wrappedLogger->log($level, $message, $context + $this->context);
+ }
+}
diff --git a/vendor/gipfl/log/src/DummyLogger.php b/vendor/gipfl/log/src/DummyLogger.php
new file mode 100644
index 0000000..3e7b0c4
--- /dev/null
+++ b/vendor/gipfl/log/src/DummyLogger.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\Log;
+
+use Psr\Log\LoggerInterface;
+
+class DummyLogger implements LoggerInterface
+{
+ public function emergency($message, array $context = [])
+ {
+ }
+
+ public function alert($message, array $context = [])
+ {
+ }
+
+ public function critical($message, array $context = [])
+ {
+ }
+
+ public function error($message, array $context = [])
+ {
+ }
+
+ public function warning($message, array $context = [])
+ {
+ }
+
+ public function notice($message, array $context = [])
+ {
+ }
+
+ public function info($message, array $context = [])
+ {
+ }
+
+ public function debug($message, array $context = [])
+ {
+ }
+
+ public function log($level, $message, array $context = [])
+ {
+ }
+}
diff --git a/vendor/gipfl/log/src/Filter/LogLevelFilter.php b/vendor/gipfl/log/src/Filter/LogLevelFilter.php
new file mode 100644
index 0000000..f26773a
--- /dev/null
+++ b/vendor/gipfl/log/src/Filter/LogLevelFilter.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace gipfl\Log\Filter;
+
+use gipfl\Log\LogFilter;
+use gipfl\Log\LogLevel;
+
+class LogLevelFilter implements LogFilter
+{
+ /** @var int */
+ protected $level;
+
+ /**
+ * @param string $level
+ */
+ public function __construct($level)
+ {
+ $this->level = LogLevel::mapNameToNumeric($level);
+ }
+
+ /**
+ * @param string $level
+ * @param string $message
+ * @param array $context
+ * @return bool
+ */
+ public function wants($level, $message, $context = [])
+ {
+ return LogLevel::mapNameToNumeric($level) <= $this->level;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLevel()
+ {
+ return LogLevel::mapNumericToName($this->level);
+ }
+}
diff --git a/vendor/gipfl/log/src/Formatter/StdOutFormatter.php b/vendor/gipfl/log/src/Formatter/StdOutFormatter.php
new file mode 100644
index 0000000..363a0b2
--- /dev/null
+++ b/vendor/gipfl/log/src/Formatter/StdOutFormatter.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace gipfl\Log\Formatter;
+
+use gipfl\Log\LogFormatter;
+use function date;
+use function microtime;
+
+class StdOutFormatter implements LogFormatter
+{
+ protected $dateFormat = 'Y-m-d H:i:s';
+
+ protected $showTimestamp = true;
+
+ public function format($level, $message, $context = [])
+ {
+ // TODO: replace placeholders!
+ return $this->renderDatePrefix() . sprintf($message, $context);
+ }
+
+ protected function renderDatePrefix()
+ {
+ if ($this->showTimestamp) {
+ return date($this->dateFormat, microtime(true));
+ }
+
+ return '';
+ }
+
+ public function setShowTimestamp($show = true)
+ {
+ $this->showTimestamp = $show;
+ }
+}
diff --git a/vendor/gipfl/log/src/IcingaWeb/IcingaLogger.php b/vendor/gipfl/log/src/IcingaWeb/IcingaLogger.php
new file mode 100644
index 0000000..d5de5e9
--- /dev/null
+++ b/vendor/gipfl/log/src/IcingaWeb/IcingaLogger.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace gipfl\Log\IcingaWeb;
+
+use Icinga\Application\Logger as IcingaApplicationLogger;
+use Icinga\Exception\ConfigurationError;
+use Psr\Log\LoggerInterface;
+
+class IcingaLogger extends IcingaApplicationLogger
+{
+ public static function replace(LoggerInterface $logger)
+ {
+ static::replaceRunningInstance(new LoggerLogWriter($logger));
+ }
+
+ public static function replaceRunningInstance(LoggerLogWriter $writer, $level = null)
+ {
+ try {
+ $instance = static::$instance;
+ if ($level !== null) {
+ $instance->setLevel($level);
+ }
+
+ $instance->writer = $writer;
+ } catch (ConfigurationError $e) {
+ static::$instance->error($e->getMessage());
+ }
+ }
+}
diff --git a/vendor/gipfl/log/src/IcingaWeb/LoggerLogWriter.php b/vendor/gipfl/log/src/IcingaWeb/LoggerLogWriter.php
new file mode 100644
index 0000000..aea4da4
--- /dev/null
+++ b/vendor/gipfl/log/src/IcingaWeb/LoggerLogWriter.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace gipfl\Log\IcingaWeb;
+
+use Icinga\Application\Logger as IcingaApplicationLogger;
+use Icinga\Application\Logger\LogWriter;
+use Icinga\Data\ConfigObject;
+use Psr\Log\LoggerInterface;
+
+class LoggerLogWriter extends LogWriter
+{
+ protected $logger;
+
+ protected static $severityMap = [
+ IcingaApplicationLogger::DEBUG => 'debug',
+ IcingaApplicationLogger::INFO => 'info',
+ IcingaApplicationLogger::WARNING => 'warning',
+ IcingaApplicationLogger::ERROR => 'error',
+ ];
+
+ public function __construct(LoggerInterface $logger)
+ {
+ parent::__construct(new ConfigObject([]));
+ $this->logger = $logger;
+ }
+
+ public function log($severity, $message)
+ {
+ $severity = static::$severityMap[$severity];
+ $this->logger->$severity($message);
+ }
+}
diff --git a/vendor/gipfl/log/src/LogFilter.php b/vendor/gipfl/log/src/LogFilter.php
new file mode 100644
index 0000000..79749f7
--- /dev/null
+++ b/vendor/gipfl/log/src/LogFilter.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace gipfl\Log;
+
+interface LogFilter
+{
+ /**
+ * @param string $level
+ * @param string $message
+ * @param array $context
+ * @return bool
+ */
+ public function wants($level, $message, $context = []);
+}
diff --git a/vendor/gipfl/log/src/LogFormatter.php b/vendor/gipfl/log/src/LogFormatter.php
new file mode 100644
index 0000000..e6529dc
--- /dev/null
+++ b/vendor/gipfl/log/src/LogFormatter.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace gipfl\Log;
+
+interface LogFormatter
+{
+ public function format($level, $message, $context = []);
+}
diff --git a/vendor/gipfl/log/src/LogLevel.php b/vendor/gipfl/log/src/LogLevel.php
new file mode 100644
index 0000000..599420e
--- /dev/null
+++ b/vendor/gipfl/log/src/LogLevel.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace gipfl\Log;
+
+use InvalidArgumentException;
+use Psr\Log\LogLevel as PsrLogLevel;
+
+class LogLevel extends PsrLogLevel
+{
+ const LEVEL_EMERGENCY = 0;
+ const LEVEL_ALERT = 1;
+ const LEVEL_CRITICAL = 2;
+ const LEVEL_ERROR = 3;
+ const LEVEL_WARNING = 4;
+ const LEVEL_NOTICE = 5;
+ const LEVEL_INFO = 6;
+ const LEVEL_DEBUG = 7;
+
+ const MAP_NAME_TO_LEVEL = [
+ self::EMERGENCY => self::LEVEL_EMERGENCY,
+ self::ALERT => self::LEVEL_ALERT,
+ self::CRITICAL => self::LEVEL_CRITICAL,
+ self::ERROR => self::LEVEL_ERROR,
+ self::WARNING => self::LEVEL_WARNING,
+ self::NOTICE => self::LEVEL_NOTICE,
+ self::INFO => self::LEVEL_INFO,
+ self::DEBUG => self::LEVEL_DEBUG,
+ ];
+
+ const MAP_LEVEL_TO_NAME = [
+ self::LEVEL_EMERGENCY => self::EMERGENCY,
+ self::LEVEL_ALERT => self::ALERT,
+ self::LEVEL_CRITICAL => self::CRITICAL,
+ self::LEVEL_ERROR => self::ERROR,
+ self::LEVEL_WARNING => self::WARNING,
+ self::LEVEL_NOTICE => self::NOTICE,
+ self::LEVEL_INFO => self::INFO,
+ self::LEVEL_DEBUG => self::DEBUG,
+ ];
+
+ /**
+ * @param string $name
+ * @return int
+ */
+ public static function mapNameToNumeric($name)
+ {
+ if (array_key_exists($name, static::MAP_NAME_TO_LEVEL)) {
+ return static::MAP_NAME_TO_LEVEL[$name];
+ }
+
+ throw new InvalidArgumentException("$name is not a valid log level name");
+ }
+
+ /**
+ * @param int $number
+ * @return string
+ */
+ public static function mapNumericToName($number)
+ {
+ if (array_key_exists($number, static::MAP_LEVEL_TO_NAME)) {
+ return static::MAP_LEVEL_TO_NAME[$number];
+ }
+
+ throw new InvalidArgumentException("$number is not a valid numeric log level");
+ }
+}
diff --git a/vendor/gipfl/log/src/LogWriter.php b/vendor/gipfl/log/src/LogWriter.php
new file mode 100644
index 0000000..8b91d5c
--- /dev/null
+++ b/vendor/gipfl/log/src/LogWriter.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace gipfl\Log;
+
+interface LogWriter
+{
+ public function write($level, $message);
+}
diff --git a/vendor/gipfl/log/src/LogWriterWithContext.php b/vendor/gipfl/log/src/LogWriterWithContext.php
new file mode 100644
index 0000000..4372eda
--- /dev/null
+++ b/vendor/gipfl/log/src/LogWriterWithContext.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace gipfl\Log;
+
+interface LogWriterWithContext extends LogWriter
+{
+ public function write($level, $message, $context = []);
+}
diff --git a/vendor/gipfl/log/src/Logger.php b/vendor/gipfl/log/src/Logger.php
new file mode 100644
index 0000000..1cbeb78
--- /dev/null
+++ b/vendor/gipfl/log/src/Logger.php
@@ -0,0 +1,169 @@
+<?php
+
+namespace gipfl\Log;
+
+use Psr\Log\LoggerInterface;
+use function array_values;
+use function spl_object_hash;
+
+class Logger implements LoggerInterface
+{
+ /** @deprecated please use LogLevel::LEVEL_EMERGENCY */
+ const LEVEL_EMERGENCY = LogLevel::LEVEL_EMERGENCY;
+ /** @deprecated please use LogLevel::LEVEL_ALERT */
+ const LEVEL_ALERT = LogLevel::LEVEL_ALERT;
+ /** @deprecated please use LogLevel::LEVEL_CRITICAL */
+ const LEVEL_CRITICAL = LogLevel::LEVEL_CRITICAL;
+ /** @deprecated please use LogLevel::LEVEL_ERROR */
+ const LEVEL_ERROR = LogLevel::LEVEL_ERROR;
+ /** @deprecated please use LogLevel::LEVEL_WARNING */
+ const LEVEL_WARNING = LogLevel::LEVEL_WARNING;
+ /** @deprecated please use LogLevel::LEVEL_NOTICE */
+ const LEVEL_NOTICE = LogLevel::LEVEL_NOTICE;
+ /** @deprecated please use LogLevel::LEVEL_INFO */
+ const LEVEL_INFO = LogLevel::LEVEL_INFO;
+ /** @deprecated please use LogLevel::LEVEL_DEBUG */
+ const LEVEL_DEBUG = LogLevel::LEVEL_DEBUG;
+ /** @deprecated Please use LogLevel::MAP_NAME_TO_LEVEL */
+ const MAP_NAME_TO_LEVEL = LogLevel::MAP_NAME_TO_LEVEL;
+
+ /** @var LogWriter[] */
+ protected $writers = [];
+
+ /** @var LogFilter[] */
+ protected $filters = [];
+
+ /**
+ * @param LogWriter $writer
+ */
+ public function addWriter(LogWriter $writer)
+ {
+ $this->writers[spl_object_hash($writer)] = $writer;
+ }
+
+ /**
+ * @param LogFilter $filter
+ */
+ public function addFilter(LogFilter $filter)
+ {
+ $this->filters[spl_object_hash($filter)] = $filter;
+ }
+
+ /**
+ * @return LogWriter[]
+ */
+ public function getWriters()
+ {
+ return array_values($this->writers);
+ }
+
+ /**
+ * @return LogFilter[]
+ */
+ public function getFilters()
+ {
+ return array_values($this->filters);
+ }
+
+ /**
+ * @param LogWriter $writer
+ */
+ public function removeWriter(LogWriter $writer)
+ {
+ unset($this->filters[spl_object_hash($writer)]);
+ }
+
+ /**
+ * @param LogFilter $filter
+ */
+ public function removeFilter(LogFilter $filter)
+ {
+ unset($this->filters[spl_object_hash($filter)]);
+ }
+
+ /**
+ * @deprecated Please use LogLevel::mapNameToNumeric()
+ */
+ public static function mapLogLevel($name)
+ {
+ return LogLevel::mapNameToNumeric($name);
+ }
+
+ public function emergency($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function alert($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function critical($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function error($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function warning($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function notice($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function info($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function debug($message, array $context = [])
+ {
+ $this->log(__FUNCTION__, $message, $context);
+ }
+
+ public function wants($level, $message, array $context = [])
+ {
+ foreach ($this->filters as $filter) {
+ if (! $filter->wants($level, $message, $context)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function log($level, $message, array $context = [])
+ {
+ if (! $this->wants($level, $message, $context)) {
+ return;
+ }
+
+ foreach ($this->writers as $writer) {
+ if ($writer instanceof LogWriterWithContext) {
+ $writer->write($level, $message, $context);
+ } else {
+ $writer->write($level, $this->formatMessage(
+ $message,
+ $context
+ ));
+ }
+ }
+ }
+
+ protected function formatMessage($message, $context = [])
+ {
+ if (empty($context)) {
+ return $message;
+ } else {
+ return \sprintf($message, $context);
+ }
+ }
+}
diff --git a/vendor/gipfl/log/src/PrefixLogger.php b/vendor/gipfl/log/src/PrefixLogger.php
new file mode 100644
index 0000000..a7273a2
--- /dev/null
+++ b/vendor/gipfl/log/src/PrefixLogger.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace gipfl\Log;
+
+use Psr\Log\LoggerInterface;
+
+class PrefixLogger extends Logger
+{
+ /** @var string */
+ protected $prefix;
+
+ /** @var LoggerInterface */
+ protected $wrappedLogger;
+
+ public function __construct($prefix, LoggerInterface $logger)
+ {
+ $this->prefix = $prefix;
+ $this->wrappedLogger = $logger;
+ }
+
+ public function log($level, $message, array $context = [])
+ {
+ $this->wrappedLogger->log($level, $this->prefix . $message, $context);
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/JournaldLogger.php b/vendor/gipfl/log/src/Writer/JournaldLogger.php
new file mode 100644
index 0000000..b3b0125
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/JournaldLogger.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\LogLevel;
+use gipfl\Log\LogWriterWithContext;
+use gipfl\SystemD\NotificationSocket;
+use React\EventLoop\LoopInterface;
+use React\Stream\WritableStreamInterface;
+
+class JournaldLogger implements LogWriterWithContext
+{
+ const JOURNALD_SOCKET = '/run/systemd/journal/socket';
+
+ protected $socket;
+
+ protected $extraFields = [];
+
+ /**
+ * SystemdStdoutWriter constructor.
+ * @param LoopInterface $loop
+ * @param WritableStreamInterface|null $stdOut
+ */
+ public function __construct($socket = null)
+ {
+ $this->socket = new NotificationSocket($socket ?: self::JOURNALD_SOCKET);
+ }
+
+ /**
+ * @param string|null $identifier
+ * @return $this
+ */
+ public function setIdentifier($identifier)
+ {
+ return $this->setExtraField('SYSLOG_IDENTIFIER', $identifier);
+ }
+
+ /**
+ * @param string $name
+ * @param ?string $value
+ * @return $this
+ */
+ public function setExtraField($name, $value)
+ {
+ if ($value === null) {
+ unset($this->extraFields[$name]);
+ } else {
+ $this->extraFields[$name] = (string) $value;
+ }
+
+ return $this;
+ }
+
+ public function write($level, $message, $context = [])
+ {
+ $this->socket->send([
+ 'MESSAGE' => $message,
+ 'PRIORITY' => LogLevel::mapNameToNumeric($level),
+ ] + $context + $this->extraFields);
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/JsonRpcConnectionWriter.php b/vendor/gipfl/log/src/Writer/JsonRpcConnectionWriter.php
new file mode 100644
index 0000000..e4042e7
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/JsonRpcConnectionWriter.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\LogWriterWithContext;
+use gipfl\Protocol\JsonRpc\JsonRpcConnection;
+use function iconv;
+use function microtime;
+
+class JsonRpcConnectionWriter implements LogWriterWithContext
+{
+ const DEFAULT_RPC_METHOD = 'logger.log';
+
+ /** @var JsonRpcConnection */
+ protected $connection;
+
+ /** @var string */
+ protected $method = self::DEFAULT_RPC_METHOD;
+
+ /** @var array */
+ protected $defaultContext;
+
+ /**
+ * @param JsonRpcConnection $connection
+ * @param array $defaultContext
+ */
+ public function __construct(JsonRpcConnection $connection, $defaultContext = [])
+ {
+ $this->connection = $connection;
+ $this->defaultContext = $defaultContext;
+ }
+
+ /**
+ * @param string $method
+ */
+ public function setMethod($method)
+ {
+ $this->method = $method;
+ }
+
+ public function write($level, $message, $context = [])
+ {
+ $message = iconv('UTF-8', 'UTF-8//IGNORE', $message);
+ $this->connection->notification($this->method, $this->defaultContext + [
+ 'level' => $level,
+ 'timestamp' => microtime(true),
+ 'message' => $message,
+ 'context' => $context,
+ ]);
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/JsonRpcWriter.php b/vendor/gipfl/log/src/Writer/JsonRpcWriter.php
new file mode 100644
index 0000000..a2fa505
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/JsonRpcWriter.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\LogWriterWithContext;
+use gipfl\Protocol\JsonRpc\Connection;
+use function iconv;
+use function microtime;
+
+/**
+ * @deprecated
+ */
+class JsonRpcWriter implements LogWriterWithContext
+{
+ const DEFAULT_RPC_METHOD = 'logger.log';
+
+ /** @var Connection */
+ protected $connection;
+
+ /** @var string */
+ protected $method = self::DEFAULT_RPC_METHOD;
+
+ /** @var array */
+ protected $defaultContext;
+
+ /**
+ * JsonRpcWriter constructor.
+ * @param Connection $connection
+ */
+ public function __construct(Connection $connection, $defaultContext = [])
+ {
+ $this->connection = $connection;
+ $this->defaultContext = $defaultContext;
+ }
+
+ /**
+ * @param string $method
+ */
+ public function setMethod($method)
+ {
+ $this->method = $method;
+ }
+
+ public function write($level, $message, $context = [])
+ {
+ $message = iconv('UTF-8', 'UTF-8//IGNORE', $message);
+ $this->connection->notification($this->method, $this->defaultContext + [
+ 'level' => $level,
+ 'timestamp' => microtime(true),
+ 'message' => $message,
+ 'context' => $context,
+ ]);
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/ProxyLogWriter.php b/vendor/gipfl/log/src/Writer/ProxyLogWriter.php
new file mode 100644
index 0000000..78d5262
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/ProxyLogWriter.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\Logger;
+use gipfl\Log\LogWriterWithContext;
+
+class ProxyLogWriter extends Logger implements LogWriterWithContext
+{
+ public function write($level, $message, $context = [])
+ {
+ $this->log($level, $message, $context);
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/SyslogWriter.php b/vendor/gipfl/log/src/Writer/SyslogWriter.php
new file mode 100644
index 0000000..86b8254
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/SyslogWriter.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\LogLevel;
+use gipfl\Log\LogWriter;
+use function openlog;
+use function syslog;
+
+class SyslogWriter implements LogWriter
+{
+ /** @var string */
+ protected $ident;
+
+ /** @var string */
+ protected $facility;
+
+ /**
+ * SyslogWriter constructor.
+ * @param string $ident
+ * @param string $facility
+ */
+ public function __construct($ident, $facility)
+ {
+ $this->ident = $ident;
+ $this->facility = $facility;
+ }
+
+ public function write($level, $message)
+ {
+ openlog($this->ident, LOG_PID, $this->facility);
+ syslog(LogLevel::mapNameToNumeric($level), str_replace("\n", ' ', $message));
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/SystemdStdoutWriter.php b/vendor/gipfl/log/src/Writer/SystemdStdoutWriter.php
new file mode 100644
index 0000000..bb66525
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/SystemdStdoutWriter.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\LogLevel;
+use gipfl\Log\LogWriter;
+use InvalidArgumentException;
+use React\EventLoop\LoopInterface;
+use React\Stream\WritableResourceStream;
+use React\Stream\WritableStreamInterface;
+use function is_int;
+use function sprintf;
+
+class SystemdStdoutWriter implements LogWriter
+{
+ // local0
+ const DEFAULT_FACILITY = 10;
+
+ /** @var WritableStreamInterface */
+ protected $stdOut;
+
+ /** @var int */
+ protected $facility = self::DEFAULT_FACILITY;
+
+ /**
+ * SystemdStdoutWriter constructor.
+ * @param LoopInterface $loop
+ * @param WritableStreamInterface|null $stdOut
+ */
+ public function __construct(LoopInterface $loop, WritableStreamInterface $stdOut = null)
+ {
+ if ($stdOut === null) {
+ $this->stdOut = new WritableResourceStream(STDOUT, $loop);
+ } else {
+ $this->stdOut = $stdOut;
+ }
+ }
+
+ /**
+ * @param int $facility
+ */
+ public function setFacility($facility)
+ {
+ if (! is_int($facility)) {
+ throw new InvalidArgumentException('Facility needs to be an integer');
+ }
+ if ($facility < 0 || $facility > 23) {
+ throw new InvalidArgumentException("Facility needs to be between 0 and 23, got $facility");
+ }
+ $this->facility = $facility;
+ }
+
+ public function write($level, $message)
+ {
+ $this->stdOut->write(sprintf(
+ "<%d>%s\n",
+ LogLevel::mapNameToNumeric($level),
+ $message
+ ));
+ }
+}
diff --git a/vendor/gipfl/log/src/Writer/WritableStreamWriter.php b/vendor/gipfl/log/src/Writer/WritableStreamWriter.php
new file mode 100644
index 0000000..4ae877f
--- /dev/null
+++ b/vendor/gipfl/log/src/Writer/WritableStreamWriter.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\Log\Writer;
+
+use gipfl\Log\LogWriter;
+use React\Stream\WritableStreamInterface;
+
+class WritableStreamWriter implements LogWriter
+{
+ const DEFAULT_SEPARATOR = PHP_EOL;
+
+ /** @var WritableStreamInterface */
+ protected $stream;
+
+ /** @var string */
+ protected $separator = self::DEFAULT_SEPARATOR;
+
+ /**
+ * WritableStreamWriter constructor.
+ * @param WritableStreamInterface $stream
+ */
+ public function __construct(WritableStreamInterface $stream)
+ {
+ $this->setStream($stream);
+ }
+
+ /**
+ * @param string $separator
+ */
+ public function setSeparator($separator)
+ {
+ $this->separator = $separator;
+ }
+
+ public function setStream(WritableStreamInterface $stream)
+ {
+ $this->stream = $stream;
+ }
+
+ public function write($level, $message)
+ {
+ $this->stream->write("$level: $message" . $this->separator);
+ }
+}
diff --git a/vendor/gipfl/openrpc/composer.json b/vendor/gipfl/openrpc/composer.json
new file mode 100644
index 0000000..dca0e6b
--- /dev/null
+++ b/vendor/gipfl/openrpc/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "gipfl/openrpc",
+ "description": "OpenRPC Connection implementation",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\OpenRpc\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ext-json": "*"
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Components.php b/vendor/gipfl/openrpc/src/Components.php
new file mode 100644
index 0000000..621a9a0
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Components.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Holds a set of reusable objects for different aspects of the OpenRPC. All
+ * objects defined within the components object will have no effect on the API
+ * unless they are explicitly referenced from properties outside the components
+ * object.
+ *
+ * All the fixed fields declared are objects that MUST use keys that match the
+ * regular expression: ^[a-zA-Z0-9\.\-_]+$
+ */
+class Components implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * An object to hold reusable Content Descriptor Objects
+ *
+ * @var ContentDescriptor[] Map[string, Content Descriptor Object]
+ */
+ public $contentDescriptors;
+
+ /**
+ * An object to hold reusable Schema Objects
+ *
+ * @var SchemaObject[] Map[string, Schema Object]
+ */
+ public $schemas;
+
+ /**
+ * An object to hold reusable Example Objects
+ *
+ * @var Example[] Map[string, Example Object]
+ */
+ public $examples;
+
+ /**
+ * An object to hold reusable Link Objects
+ *
+ * @var Link[] Map[string, Link Object]
+ */
+ public $links;
+
+ /**
+ * An object to hold reusable Error Objects
+ *
+ * @var Error[] Map[string, Error Object]
+ */
+ public $errors;
+
+ /**
+ * An object to hold reusable Example Pairing Objects
+ *
+ * @var ExamplePairing[] Map[string, Example Pairing Object]
+ */
+ public $examplePairingObjects;
+
+ /**
+ * An object to hold reusable Tag Objects
+ *
+ * @var TagObject[] Map[string, Tag Object]
+ */
+ public $tags;
+}
diff --git a/vendor/gipfl/openrpc/src/Contact.php b/vendor/gipfl/openrpc/src/Contact.php
new file mode 100644
index 0000000..e957719
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Contact.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Contact information for the exposed API
+ */
+class Contact implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * The identifying name of the contact person/organization
+ *
+ * @var string|null
+ */
+ public $name;
+
+ /**
+ * The URL pointing to the contact information. MUST be in the format of a
+ * URL.
+ *
+ * @var string|null
+ */
+ public $url;
+
+ /**
+ * The email address of the contact person/organization. MUST be in the
+ * format of an email address.
+ *
+ * @var string|null
+ */
+ public $email;
+
+ /**
+ * Contact constructor.
+ * @param string|null $name
+ * @param string|null $url
+ * @param string|null $email
+ */
+ public function __construct($name = null, $url = null, $email = null)
+ {
+ $this->name = $name;
+ $this->url = $url;
+ $this->email = $email;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/ContentDescriptor.php b/vendor/gipfl/openrpc/src/ContentDescriptor.php
new file mode 100644
index 0000000..42772fb
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/ContentDescriptor.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Content Descriptors are objects that do just as they suggest - describe
+ * content. They are reusable ways of describing either parameters or result.
+ * They MUST have a schema.
+ */
+class ContentDescriptor implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. Name of the content that is being described. If the content
+ * described is a method parameter assignable by-name, this field SHALL
+ * define the parameter’s key (ie name).
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * A short summary of the content that is being described.
+ *
+ * @var string|null
+ */
+ public $summary;
+
+ /**
+ * A verbose explanation of the content descriptor behavior. GitHub Flavored
+ * Markdown syntax MAY be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * Determines if the content is a required field. Default value is false.
+ *
+ * @var boolean|null
+ */
+ public $required;
+
+ /**
+ * REQUIRED. Schema that describes the content.
+ *
+ * The Schema Object allows the definition of input and output data types.
+ * The Schema Objects MUST follow the specifications outline in the JSON
+ * Schema Specification 7 Alternatively, any time a Schema Object can be
+ * used, a Reference Object can be used in its place. This allows referencing
+ * definitions instead of defining them inline.
+ *
+ * @var SchemaObject
+ */
+ public $schema;
+
+ /**
+ * Specifies that the content is deprecated and SHOULD be transitioned out
+ * of usage. Default value is false.
+ *
+ * @var boolean|null
+ */
+ public $deprecated;
+
+ public function __construct($name, $schema)
+ {
+ $this->name = $name;
+ $this->schema = $schema;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Error.php b/vendor/gipfl/openrpc/src/Error.php
new file mode 100644
index 0000000..25ed818
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Error.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Defines an application level error.
+ */
+class Error implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * Application Defined Error Code
+ *
+ * REQUIRED. A Number that indicates the error type that occurred. This
+ * MUST be an integer. The error codes from and including -32768 to -32000
+ * are reserved for pre-defined errors. These pre-defined errors SHOULD be
+ * assumed to be returned from any JSON-RPC api.
+ *
+ * @var int
+ */
+ public $code;
+
+ /**
+ * REQUIRED. A String providing a short description of the error. The
+ * message SHOULD be limited to a concise single sentence.
+ *
+ * @var string
+ */
+ public $message;
+
+ /**
+ * A Primitive or Structured value that contains additional information
+ * about the error. This may be omitted. The value of this member is defined
+ * by the Server (e.g. detailed error information, nested errors etc.).
+ *
+ * @var mixed
+ */
+ public $data;
+
+ /**
+ * @param int $code
+ * @param string $message
+ * @param mixed|null $data
+ */
+ public function __construct($code, $message, $data = null)
+ {
+ $this->code = $code;
+ $this->message = $message;
+ $this->data = $data;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Example.php b/vendor/gipfl/openrpc/src/Example.php
new file mode 100644
index 0000000..1e5aefe
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Example.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * The Example object is an object the defines an example that is intended to
+ * match a given Content Descriptor Schema. If the Content Descriptor Schema
+ * includes examples, the value from this Example Object supersedes the value
+ * of the schema example.
+ *
+ * In all cases, the example vaJsonSerializablelue is expected to be compatible with the type
+ * schema of its associated value. Tooling implementations MAY choose to
+ * validate compatibility automatically, and reject the example value(s) if
+ * incompatible.
+ */
+class Example implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /** @var string|null Name for the example pairing */
+ public $name;
+
+ /** @var string|null A verbose explanation of the example pairing */
+ public $summary;
+
+ /** @var string|null Short description for the example pairing */
+ public $description;
+
+ /** @var <Example|Reference>[] Example parameters */
+ public $params;
+
+ /** @var Example|Reference Example result */
+ public $result;
+}
diff --git a/vendor/gipfl/openrpc/src/ExamplePairing.php b/vendor/gipfl/openrpc/src/ExamplePairing.php
new file mode 100644
index 0000000..6f34137
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/ExamplePairing.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * The example Pairing object consists of a set of example params and result.
+ * The result is what you can expect from the JSON-RPC service given the exact
+ * params.
+ */
+class ExamplePairing implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /** @var string|null Name for the example pairing */
+ public $name;
+
+ /** @var string|null A verbose explanation of the example pairing */
+ public $summary;
+
+ /** @var string|null Short description for the example pairing */
+ public $description;
+
+ /** @var <Example|Reference>[] Example parameters */
+ public $params;
+
+ /** @var Example|Reference Example result */
+ public $result;
+}
diff --git a/vendor/gipfl/openrpc/src/ExternalDocumentation.php b/vendor/gipfl/openrpc/src/ExternalDocumentation.php
new file mode 100644
index 0000000..54b5883
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/ExternalDocumentation.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Allows referencing an external resource for extended documentation
+ */
+class ExternalDocumentation implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. The URL for the target documentation. Value MUST be in the
+ * format of a URL.
+ *
+ * @var string
+ */
+ public $url;
+
+ /**
+ * A verbose explanation of the target documentation. GitHub Flavored Markdown
+ * syntax MAY be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * @param $url
+ */
+ public function __construct($url)
+ {
+ $this->url = $url;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Info.php b/vendor/gipfl/openrpc/src/Info.php
new file mode 100644
index 0000000..3c957c9
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Info.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * The object provides metadata about the API. The metadata MAY be used by the
+ * clients if needed, and MAY be presented in editing or documentation
+ * generation tools for convenience.
+ */
+class Info implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. The title of the application
+ *
+ * @var string
+ */
+ public $title;
+ /**
+ * A verbose description of the application. GitHub Flavored Markdown syntax
+ * MAY be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * A URL to the Terms of Service for the API. MUST be in the format of a URL
+ *
+ * @var string|null
+ */
+ public $termsOfService;
+
+ /**
+ * The contact information for the exposed API
+ *
+ * @var Contact|null
+ */
+ public $contact;
+
+ /**
+ * The license information for the exposed API
+ *
+ * @var License|null
+ */
+ public $license;
+
+ /**
+ * REQUIRED. The version of the OpenRPC document (which is distinct from the
+ * OpenRPC Specification version or the API implementation version)
+ *
+ * @var string
+ */
+ public $version;
+
+ /**
+ * Info constructor.
+ * @param string $title
+ * @param string $version
+ */
+ public function __construct($title, $version)
+ {
+ $this->title = $title;
+ $this->version = $version;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/License.php b/vendor/gipfl/openrpc/src/License.php
new file mode 100644
index 0000000..3bb8904
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/License.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * License information for the exposed API.
+ */
+class License implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. The license name used for the API.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * A URL to the license used for the API. MUST be in the format of a URL.
+ *
+ * @var string|null
+ */
+ public $url;
+
+ /**
+ * @param string $name
+ * @param string|null $url
+ */
+ public function __construct($name, $url = null)
+ {
+ $this->name = $name;
+ $this->url = $url;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Link.php b/vendor/gipfl/openrpc/src/Link.php
new file mode 100644
index 0000000..d0ac517
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Link.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * The Link object represents a possible design-time link for a result. The
+ * presence of a link does not guarantee the caller’s ability to successfully
+ * invoke it, rather it provides a known relationship and traversal mechanism
+ * between results and other methods.
+ *
+ * Unlike dynamic links (i.e. links provided in the result payload), the OpenRPC
+ * linking mechanism does not require link information in the runtime result.
+ *
+ * For computing links, and providing instructions to execute them, a runtime
+ * expression is used for accessing values in an method and using them as
+ * parameters while invoking the linked method.
+ */
+class Link implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. Canonical name of the link.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * Short description for the link.
+ *
+ * @var string|null
+ */
+ public $summary;
+
+ /**
+ * A description of the link. GitHub Flavored Markdown syntax MAY be used
+ * for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * The name of an existing, resolvable OpenRPC method, as defined with a
+ * unique method. This field MUST resolve to a unique Method Object. As
+ * opposed to Open Api, Relative method values ARE NOT permitted.
+ *
+ * @var string|null
+ */
+ public $method;
+
+ /**
+ * A map representing parameters to pass to a method as specified with
+ * method. The key is the parameter name to be used, whereas the value can
+ * be a constant or a runtime expression to be evaluated and passed to the
+ * linked method.
+ *
+ * A linked method must be identified directly, and must exist in the list
+ * of methods defined by the Methods Object.
+ *
+ * When a runtime expression fails to evaluate, no parameter value is passed
+ * to the target method.
+ *
+ * Values from the result can be used to drive a linked method.
+ *
+ * Clients follow all links at their discretion. Neither permissions, nor
+ * the capability to make a successful call to that link, is guaranteed
+ * solely by the existence of a relationship.
+ *
+ * @var array Map[string, Any | RuntimeExpression]
+ */
+ public $params;
+
+ /**
+ * A server object to be used by the target method.
+ *
+ * @var Server|null
+ */
+ public $server;
+
+ /**
+ * @param string $name
+ */
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Method.php b/vendor/gipfl/openrpc/src/Method.php
new file mode 100644
index 0000000..54226e3
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Method.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Describes the interface for the given method name. The method name is used
+ * as the method field of the JSON-RPC body. It therefore MUST be unique.
+ */
+class Method implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. The cannonical name for the method. The name MUST be unique
+ * within the methods array.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * A list of tags for API documentation control. Tags can be used for
+ * logical grouping of methods by resources or any other qualifier.
+ *
+ * @var TagObject[]|Reference[]
+ */
+ public $tags;
+
+ /**
+ * A short summary of what the method does
+ *
+ * @var string|null
+ */
+ public $summary;
+
+ /**
+ * A verbose explanation of the method behavior. GitHub Flavored Markdown
+ * syntax MAY be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * Additional external documentation for this method
+ *
+ * @var ExternalDocumentation
+ */
+ public $externalDocs;
+
+ /**
+ * REQUIRED. A list of parameters that are applicable for this method. The
+ * list MUST NOT include duplicated parameters and therefore require name
+ * to be unique. The list can use the Reference Object to link to parameters
+ * that are defined by the Content Descriptor Object. All optional params
+ * (content descriptor objects with “required”: false) MUST be positioned
+ * after all required params in the list.
+ *
+ * @var <ContentDescriptor|Reference>[]
+ */
+ public $params;
+
+ /**
+ * REQUIRED. The description of the result returned by the method. It MUST
+ * be a Content Descriptor.
+ *
+ * @var ContentDescriptor|Reference
+ */
+ public $result;
+
+ /**
+ * Declares this method to be deprecated. Consumers SHOULD refrain from
+ * usage of the declared method. Default value is false.
+ *
+ * @var boolean
+ */
+ public $deprecated;
+
+ /**
+ * An alternative servers array to service this method. If an alternative
+ * servers array is specified at the Root level, it will be overridden by
+ * this value.
+ *
+ * @var Server[]
+ */
+ public $servers;
+
+ /**
+ * A list of custom application defined errors that MAY be returned. The
+ * Errors MUST have unique error codes.
+ *
+ * @var <Error|Reference>[]
+ */
+ public $errors;
+
+ /**
+ * A list of possible links from this method call
+ *
+ * @var <Link|Reference>[]
+ */
+ public $links;
+
+ /**
+ * The expected format of the parameters. As per the JSON-RPC 2.0 specification,
+ * the params of a JSON-RPC request object may be an array, object, or either
+ * (represented as by-position, by-name, and either respectively). When a method
+ * has a paramStructure value of by-name, callers of the method MUST send a
+ * JSON-RPC request object whose params field is an object. Further, the key
+ * names of the params object MUST be the same as the contentDescriptor.names
+ * for the given method. Defaults to "either".
+ *
+ * @var string "by-name" | "by-position" | "either"
+ */
+ public $paramStructure;
+
+ /**
+ * Array of Example Pairing Object where each example includes a valid
+ * params-to-result Content Descriptor pairing.
+ *
+ * @var ExamplePairing []
+ */
+ public $examples;
+
+ /**
+ * @param $name
+ */
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/OpenRpcDocument.php b/vendor/gipfl/openrpc/src/OpenRpcDocument.php
new file mode 100644
index 0000000..62be245
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/OpenRpcDocument.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * This is the root object of the OpenRPC document. The contents of this object
+ * represent a whole OpenRPC document. How this object is constructed or stored
+ * is outside the scope of the OpenRPC Specification.
+ */
+class OpenRpcDocument implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. This string MUST be the semantic version number of the OpenRPC
+ * Specification version that the OpenRPC document uses. The openrpc field
+ * SHOULD be used by tooling specifications and clients to interpret the
+ * OpenRPC document. This is not related to the API info.version string.
+ *
+ * @var string
+ */
+ public $openrpc;
+
+ /**
+ * REQUIRED. Provides metadata about the API. The metadata MAY be used by
+ * tooling as required.
+ *
+ * @var Info
+ */
+ public $info;
+
+ /**
+ * An array of Server Objects, which provide connectivity information to a
+ * target server. If the servers property is not provided, or is an empty
+ * array, the default value would be a Server Object with a url value of
+ * localhost.
+ *
+ * @var Server[]|null
+ */
+ public $servers;
+
+ /**
+ * REQUIRED. The available methods for the API. While it is required, the
+ * array may be empty (to handle security filtering, for example).
+ *
+ * @var Method[]|Reference[]
+ */
+ public $methods = [];
+
+ /**
+ * An element to hold various schemas for the specification
+ *
+ * @var Components|null
+ */
+ public $components;
+
+ /**
+ * Additional external documentation
+ *
+ * @var ExternalDocumentation|null
+ */
+ public $externalDocs;
+
+ /**
+ * @param string $openRpcVersion
+ * @param Info $info
+ */
+ public function __construct($openRpcVersion, Info $info)
+ {
+ $this->openrpc = $openRpcVersion;
+ $this->info = $info;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reference.php b/vendor/gipfl/openrpc/src/Reference.php
new file mode 100644
index 0000000..e954293
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reference.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * A simple object to allow referencing other components in the specification,
+ * internally and externally.
+ *
+ * The Reference Object is defined by JSON Schema and follows the same structure,
+ * behavior and rules.
+ */
+class Reference implements JsonSerializable
+{
+ /** @var string REQUIRED. The reference string */
+ public $ref;
+
+ /**
+ * @param string $ref
+ */
+ public function __construct($ref)
+ {
+ $this->ref = $ref;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return (object) ['$ref' => $this->ref];
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/MetaDataClass.php b/vendor/gipfl/openrpc/src/Reflection/MetaDataClass.php
new file mode 100644
index 0000000..4e9427e
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/MetaDataClass.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+use InvalidArgumentException;
+use ReflectionClass;
+use ReflectionException;
+use function lcfirst;
+use function preg_match;
+
+class MetaDataClass
+{
+ /** @var MetaDataMethod[] */
+ public $methods = [];
+
+ /** @var string|null */
+ public $error;
+
+ /**
+ * @param string $class
+ * @throws ReflectionException
+ * @return static
+ */
+ public static function analyze($class)
+ {
+ $info = new static();
+
+ $ref = new ReflectionClass($class);
+
+ foreach ($ref->getMethods() as $method) {
+ $methodName = $method->getName();
+ if (! preg_match('/^(.+)(Request|Notification)$/', $methodName, $match)) {
+ continue;
+ }
+
+ $info->addMethod(MethodCommentParser::parseMethod(
+ $match[1],
+ lcfirst($match[2]),
+ $method->getDocComment()
+ ));
+ }
+
+ return $info;
+ }
+
+ public function addMethod(MetaDataMethod $method)
+ {
+ $name = $method->name;
+ if (isset($this->methods[$name])) {
+ throw new InvalidArgumentException("Cannot add method '$name' twice");
+ }
+
+ $this->methods[$name] = $method;
+ }
+
+ /**
+ * @return MetaDataMethod[]
+ */
+ public function getMethods()
+ {
+ return $this->methods;
+ }
+
+ /**
+ * @param $name
+ * @return MetaDataMethod
+ */
+ public function getMethod($name)
+ {
+ if (isset($this->methods[$name])) {
+ return $this->methods[$name];
+ }
+
+ throw new InvalidArgumentException("There is no '$name' method");
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/MetaDataMethod.php b/vendor/gipfl/openrpc/src/Reflection/MetaDataMethod.php
new file mode 100644
index 0000000..e711e6e
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/MetaDataMethod.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+use InvalidArgumentException;
+
+class MetaDataMethod
+{
+ /** @var string */
+ public $name;
+
+ /** @var string Either 'request' or 'notification' */
+ public $requestType;
+
+ /** @var string */
+ public $resultType;
+
+ /** @var MetaDataParameter[] */
+ public $parameters = [];
+
+ /** @var string */
+ public $title;
+
+ /** @var string */
+ public $description;
+
+ public function __construct($name, $requestType)
+ {
+ $this->name = $name;
+ $this->requestType = $requestType;
+ }
+
+ public function addParsed(MethodCommentParser $parser)
+ {
+ $this->resultType = $parser->getResultType();
+ $this->parameters = $parser->getParams();
+ $this->title = $parser->getTitle();
+ $this->description = $parser->getDescription();
+
+ return $this;
+ }
+
+ /**
+ * @param MetaDataParameter $parameter
+ */
+ public function addParameter(MetaDataParameter $parameter)
+ {
+ $this->parameters[$parameter->getName()] = $parameter;
+ }
+
+ /**
+ * @param $name
+ * @return MetaDataParameter
+ */
+ public function getParameter($name)
+ {
+ if (isset($this->parameters[$name])) {
+ return $this->parameters[$name];
+ }
+
+ throw new InvalidArgumentException("There is no '$name' parameter" . print_r($this->parameters, 1));
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRequestType()
+ {
+ return $this->requestType;
+ }
+
+ /**
+ * @return string
+ */
+ public function getResultType()
+ {
+ return $this->resultType ?: 'void';
+ }
+
+ /**
+ * @return MetaDataParameter[]
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/MetaDataParameter.php b/vendor/gipfl/openrpc/src/Reflection/MetaDataParameter.php
new file mode 100644
index 0000000..7ed76dd
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/MetaDataParameter.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class MetaDataParameter
+{
+ /** @var string */
+ public $name;
+
+ /** @var string */
+ public $type;
+
+ /** @var string */
+ public $description;
+
+ public function __construct($name, $type, $description = null)
+ {
+ $this->name = $name;
+ $this->type = $type;
+ $this->description = $description;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/MetaDataTagParser.php b/vendor/gipfl/openrpc/src/Reflection/MetaDataTagParser.php
new file mode 100644
index 0000000..d1df585
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/MetaDataTagParser.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class MetaDataTagParser
+{
+ const DEFAULT_TAG_TYPE = Tag::class;
+
+ const SPECIAL_TAGS = [
+ 'param' => ParamTag::class,
+ 'throws' => ThrowsTag::class,
+ 'return' => ReturnTag::class,
+ ];
+
+ protected $tagType;
+
+ protected $string;
+
+ public function __construct($tagType, $string)
+ {
+ $this->tagType = $tagType;
+ $this->string = $string;
+ }
+
+ public function getTag()
+ {
+ $type = $this->getTagType();
+ $tags = static::SPECIAL_TAGS;
+ if (isset($tags[$type])) {
+ $class = self::SPECIAL_TAGS[$type];
+ } else {
+ $class = self::DEFAULT_TAG_TYPE;
+ }
+
+ return new $class($type, $this->getString());
+ }
+
+ /**
+ * @return string
+ */
+ public function getTagType()
+ {
+ return $this->tagType;
+ }
+
+ /**
+ * @return string
+ */
+ public function getString()
+ {
+ return $this->string;
+ }
+
+ public function appendValueString($string)
+ {
+ $this->string .= $string;
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/MetaDataTagSet.php b/vendor/gipfl/openrpc/src/Reflection/MetaDataTagSet.php
new file mode 100644
index 0000000..a246fcb
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/MetaDataTagSet.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class MetaDataTagSet
+{
+ /** @var Tag[] */
+ protected $tags;
+
+ public function __construct()
+ {
+ $this->tags = [];
+ }
+
+ public function add(Tag $tag)
+ {
+ $this->tags[] = $tag;
+ }
+
+ /**
+ * @param string $type
+ * @return static
+ */
+ public function byType($type)
+ {
+ $set = new static();
+ foreach ($this->tags as $tag) {
+ if ($tag->tagType === $type) {
+ $set->add($tag);
+ }
+ }
+
+ return $set;
+ }
+
+ /**
+ * @return MetaDataParameter[]
+ */
+ public function getParams()
+ {
+ $result = [];
+ foreach ($this->byType('param')->getTags() as $tag) {
+ assert($tag instanceof ParamTag);
+ $result[] = new MetaDataParameter($tag->name, $tag->dataType, $tag->description);
+ // TODO: variadic!
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getReturnType()
+ {
+ foreach ($this->byType('return')->getTags() as $tag) {
+ assert($tag instanceof ReturnTag);
+ // TODO: return a class, we need the description
+ return $tag->dataType;
+ }
+
+ return null;
+ }
+
+ public function getTags()
+ {
+ return $this->tags;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/MethodCommentParser.php b/vendor/gipfl/openrpc/src/Reflection/MethodCommentParser.php
new file mode 100644
index 0000000..6f20f93
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/MethodCommentParser.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+use function explode;
+use function implode;
+use function preg_match;
+use function preg_replace;
+use function substr;
+use function trim;
+
+class MethodCommentParser
+{
+ const REGEXP_START_OF_COMMENT = '~^\s*/\*\*\n~s';
+ const REGEXP_COMMENT_LINE_START = '~^\s*\*\s?~';
+ const REGEXP_END_OF_COMMENT = '~\n\s*\*/\s*~s';
+ const REGEXP_TAG_TYPE_VALUE = '/^@([A-z0-9]+)\s+(.+?)$/';
+
+ protected $paragraphs = [];
+ protected $currentParagraph;
+
+ /** @var MetaDataMethod */
+ protected $meta;
+
+ /** @var MetaDataTagParser|null */
+ protected $currentTag;
+
+ /** @var MetaDataTagSet */
+ protected $tags;
+
+ protected function __construct(MetaDataMethod $meta)
+ {
+ $this->meta = $meta;
+ $this->tags = new MetaDataTagSet();
+ }
+
+ public function getTitle()
+ {
+ return $this->meta->title;
+ }
+
+ public function getParams()
+ {
+ return $this->getTags()->getParams();
+ }
+
+ public function getResultType()
+ {
+ return $this->getTags()->getReturnType();
+ }
+
+ public function getDescription()
+ {
+ return implode("\n", $this->paragraphs);
+ }
+
+ public function getTags()
+ {
+ return $this->tags;
+ }
+
+ protected function parseLine($line)
+ {
+ // Strip * at line start
+ $line = preg_replace(self::REGEXP_COMMENT_LINE_START, '', $line);
+ $line = trim($line);
+ if (preg_match(self::REGEXP_TAG_TYPE_VALUE, $line, $match)) {
+ $this->finishCurrentObjects();
+ $this->currentTag = new MetaDataTagParser($match[1], $match[2]);
+ return;
+ }
+
+ if ($this->currentTag) {
+ $this->currentTag->appendValueString($line);
+ return;
+ }
+
+ $this->eventuallyFinishCurrentTag();
+ $this->appendToParagraph($line);
+ }
+
+ protected function appendToParagraph($line)
+ {
+ if (trim($line) === '') {
+ $this->eventuallyFinishCurrentLine();
+ return;
+ }
+
+ if ($this->currentParagraph === null) {
+ $this->currentParagraph = & $this->paragraphs[];
+ $this->currentParagraph = $line;
+ } else {
+ if (substr($line, 0, 2) === ' ') {
+ $this->currentParagraph .= "\n" . $line;
+ } else {
+ $this->currentParagraph .= ' ' . $line;
+ }
+ }
+ }
+
+ protected function finishCurrentObjects()
+ {
+ $this->eventuallyFinishCurrentTag();
+ $this->eventuallyFinishCurrentLine();
+ }
+
+ protected function eventuallyFinishCurrentTag()
+ {
+ if ($this->currentTag) {
+ $this->tags->add($this->currentTag->getTag());
+ $this->currentTag = null;
+ }
+ }
+
+ protected function eventuallyFinishCurrentLine()
+ {
+ if ($this->currentParagraph !== null) {
+ unset($this->currentParagraph);
+ $this->currentParagraph = null;
+ }
+ }
+
+ protected function parse($plain)
+ {
+ foreach (explode("\n", $plain) as $line) {
+ $this->parseLine($line);
+ }
+ $this->finishCurrentObjects();
+ }
+
+ public static function parseMethod($methodName, $methodType, $raw)
+ {
+ $meta = new MetaDataMethod($methodName, $methodType);
+ $self = new static($meta);
+ $plain = (string) $raw;
+ static::stripStartOfComment($plain);
+ static::stripEndOfComment($plain);
+ $self->parse($plain);
+ $meta->addParsed($self);
+
+ return $meta;
+ }
+
+ /**
+ * Removes comment start -> /**
+ *
+ * @param $string
+ */
+ protected static function stripStartOfComment(&$string)
+ {
+ $string = preg_replace(self::REGEXP_START_OF_COMMENT, '', $string);
+ }
+
+ /**
+ * Removes comment end -> * /
+ *
+ * @param $string
+ */
+ protected static function stripEndOfComment(&$string)
+ {
+ $string = preg_replace(self::REGEXP_END_OF_COMMENT, "\n", $string);
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/ParamTag.php b/vendor/gipfl/openrpc/src/Reflection/ParamTag.php
new file mode 100644
index 0000000..32a8b33
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/ParamTag.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class ParamTag extends Tag
+{
+ public $name;
+
+ public $dataType;
+
+ public $description;
+
+ public $isVariadic = false;
+
+ protected function parseTagValue($value)
+ {
+ $parts = preg_split('/(\s+)/us', $value, 3, PREG_SPLIT_DELIM_CAPTURE);
+ if (substr($parts[0], 0, 1) !== '$' && substr($parts[0], 0, 4) !== '...$') {
+ $this->dataType = array_shift($parts);
+ array_shift($parts);
+ }
+ if (empty($parts)) {
+ return;
+ }
+
+ if (substr($parts[0], 0, 1) === '$') {
+ $this->name = substr($parts[0], 1);
+ array_shift($parts);
+ array_shift($parts);
+ } elseif (substr($parts[0], 0, 4) !== '...$') {
+ $this->name = substr($parts[0], 4);
+ $this->isVariadic = true;
+ array_shift($parts);
+ array_shift($parts);
+ }
+
+ if (! empty($parts)) {
+ $this->description = implode($parts);
+ }
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/ReturnTag.php b/vendor/gipfl/openrpc/src/Reflection/ReturnTag.php
new file mode 100644
index 0000000..0a147cf
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/ReturnTag.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class ReturnTag extends TypeDescriptionTag
+{
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/Tag.php b/vendor/gipfl/openrpc/src/Reflection/Tag.php
new file mode 100644
index 0000000..038cb53
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/Tag.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class Tag
+{
+ /** @var string */
+ public $tagType;
+
+ /** @var string */
+ public $tagValue;
+
+ public function __construct($tagType, $tagValue)
+ {
+ $this->tagType = $tagType;
+ $this->setTagValue($tagValue);
+ $this->parseTagValue(trim($tagValue));
+ }
+
+ public function setTagValue($value)
+ {
+ $this->tagValue = $value;
+
+ return $this;
+ }
+
+ /**
+ * Parse Tag value into Tag-specific properties
+ *
+ * Override this method for specific tag types
+ *
+ * @param $tagValue
+ */
+ protected function parseTagValue($tagValue)
+ {
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/ThrowsTag.php b/vendor/gipfl/openrpc/src/Reflection/ThrowsTag.php
new file mode 100644
index 0000000..42d1fd6
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/ThrowsTag.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class ThrowsTag extends TypeDescriptionTag
+{
+}
diff --git a/vendor/gipfl/openrpc/src/Reflection/TypeDescriptionTag.php b/vendor/gipfl/openrpc/src/Reflection/TypeDescriptionTag.php
new file mode 100644
index 0000000..fd090b2
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Reflection/TypeDescriptionTag.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace gipfl\OpenRpc\Reflection;
+
+class TypeDescriptionTag extends Tag
+{
+ public $dataType;
+
+ public $description;
+
+ protected function parseTagValue($value)
+ {
+ if (empty($value)) {
+ return;
+ }
+ $parts = preg_split('/(\s+)/us', trim($value), 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ $this->dataType = array_shift($parts);
+ array_shift($parts);
+
+ if (empty($parts)) {
+ return;
+ }
+
+ $this->description = implode($parts);
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/Server.php b/vendor/gipfl/openrpc/src/Server.php
new file mode 100644
index 0000000..0d03c9f
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/Server.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * An object representing a Server.
+ */
+class Server implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. A name to be used as the canonical name for the server.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * REQUIRED. A URL to the target host. This URL supports Server Variables and
+ * MAY be relative, to indicate that the host location is relative to the
+ * location where the OpenRPC document is being served. Server Variables are
+ * passed into the Runtime Expression to produce a server URL.
+ *
+ * Runtime expressions allow the user to define an expression which will
+ * evaluate to a string once the desired value(s) are known. They are used
+ * when the desired value of a link or server can only be constructed at
+ * run time. This mechanism is used by Link Objects and Server Variables.
+ *
+ * The runtime expression makes use of JSON Template Language syntax:
+ *
+ * https://tools.ietf.org/html/draft-jonas-json-template-language-01
+ *
+ * Runtime expressions preserve the type of the referenced value.
+ *
+ * @var string RuntimeExpression
+ */
+ public $url;
+
+ /**
+ * A short summary of what the server is.
+ *
+ * @var string|null
+ */
+ public $summary;
+
+ /**
+ * An optional string describing the host designated by the URL. GitHub
+ * Flavored Markdown syntax MAY be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * A map between a variable name and its value. The value is passed into
+ * the Runtime Expression to produce a server URL.
+ *
+ * @var ServerVariable Map[string, Server Variable Object]
+ */
+ public $variables;
+
+ /**
+ * @param string $name
+ * @param string $url
+ */
+ public function __construct($name, $url)
+ {
+ $this->name = $name;
+ $this->url = $url;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/ServerVariable.php b/vendor/gipfl/openrpc/src/ServerVariable.php
new file mode 100644
index 0000000..047dcf6
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/ServerVariable.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * An object representing a Server Variable for server URL template substitution.
+ */
+class ServerVariable implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. The default value to use for substitution, which SHALL be sent
+ * if an alternate value is not supplied. Note this behavior is different
+ * than the Schema Object’s treatment of default values, because in those
+ * cases parameter values are optional.
+ *
+ * @var string
+ */
+ public $default;
+
+ /**
+ * An optional description for the server variable. GitHub Flavored Markdown
+ * syntax MAY be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * An enumeration of string values to be used if the substitution options are from a limited set.
+ *
+ * @var string[]
+ */
+ public $enum;
+
+ /**
+ * @param string $default
+ */
+ public function __construct($default)
+ {
+ $this->default = $default;
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/SimpleJsonSerializer.php b/vendor/gipfl/openrpc/src/SimpleJsonSerializer.php
new file mode 100644
index 0000000..0427c7b
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/SimpleJsonSerializer.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+trait SimpleJsonSerializer
+{
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return (object) array_filter(get_object_vars($this), function ($value) {
+ return $value !== null;
+ });
+ }
+}
diff --git a/vendor/gipfl/openrpc/src/TagObject.php b/vendor/gipfl/openrpc/src/TagObject.php
new file mode 100644
index 0000000..2d69d1d
--- /dev/null
+++ b/vendor/gipfl/openrpc/src/TagObject.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace gipfl\OpenRpc;
+
+use JsonSerializable;
+
+/**
+ * Adds metadata to a single tag that is used by the Method Object. It is not
+ * mandatory to have a Tag Object per tag defined in the Method Object instances
+ */
+class TagObject implements JsonSerializable
+{
+ use SimpleJsonSerializer;
+
+ /**
+ * REQUIRED. The name of the tag.
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * A short summary of the tag.
+ *
+ * @var string|null
+ */
+ public $summary;
+
+ /**
+ * A verbose explanation for the tag. GitHub Flavored Markdown syntax MAY
+ * be used for rich text representation.
+ *
+ * @var string|null
+ */
+ public $description;
+
+ /**
+ * Additional external documentation for this tag.
+ *
+ * @var ExternalDocumentation
+ */
+ public $externalDocs;
+
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+}
diff --git a/vendor/gipfl/process/composer.json b/vendor/gipfl/process/composer.json
new file mode 100644
index 0000000..21e9971
--- /dev/null
+++ b/vendor/gipfl/process/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "gipfl/process",
+ "description": "Process-related utility classes",
+ "type": "library",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Process\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "require": {
+ "php": ">=5.6.0",
+ "ext-json": "*",
+ "react/child-process": ">=0.6",
+ "react/promise": "^2",
+ "gipfl/json": ">=0.1",
+ "gipfl/linux-health": ">=0.2"
+ }
+}
diff --git a/vendor/gipfl/process/src/FinishedProcessState.php b/vendor/gipfl/process/src/FinishedProcessState.php
new file mode 100644
index 0000000..fd70292
--- /dev/null
+++ b/vendor/gipfl/process/src/FinishedProcessState.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace gipfl\Process;
+
+class FinishedProcessState
+{
+ /** @var int|null */
+ protected $exitCode;
+
+ /** @var int|null */
+ protected $termSignal;
+
+ public function __construct($exitCode, $termSignal)
+ {
+ $this->exitCode = $exitCode;
+ $this->termSignal = $termSignal;
+ }
+
+ public function succeeded()
+ {
+ return $this->exitCode === 0;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getExitCode()
+ {
+ return $this->exitCode;
+ }
+
+ public function getTermSignal()
+ {
+ return $this->termSignal;
+ }
+
+ public function getCombinedExitCode()
+ {
+ if ($this->exitCode === null) {
+ if ($this->termSignal === null) {
+ return 255;
+ } else {
+ return 255 + $this->termSignal;
+ }
+ } else {
+ return $this->exitCode;
+ }
+ }
+
+ public function getReason()
+ {
+ if ($this->exitCode === null) {
+ if ($this->termSignal === null) {
+ return 'Process died';
+ } else {
+ return 'Process got terminated with SIGNAL ' . $this->termSignal;
+ }
+ } else {
+ if ($this->exitCode === 0) {
+ return 'Process finished successfully';
+ } else {
+ return 'Process exited with exit code ' . $this->exitCode;
+ }
+ }
+ }
+}
diff --git a/vendor/gipfl/process/src/ProcessInfo.php b/vendor/gipfl/process/src/ProcessInfo.php
new file mode 100644
index 0000000..db740d1
--- /dev/null
+++ b/vendor/gipfl/process/src/ProcessInfo.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace gipfl\Process;
+
+use gipfl\Json\JsonSerialization;
+use gipfl\LinuxHealth\Memory;
+use React\ChildProcess\Process;
+
+class ProcessInfo implements JsonSerialization
+{
+ /** @var ?int */
+ protected $pid;
+
+ /** @var string */
+ protected $command;
+
+ /** @var bool */
+ protected $running;
+
+ /** @var ?object */
+ protected $memory;
+
+ public static function forProcess(Process $process)
+ {
+ $self = new static();
+ $self->pid = $process->getPid();
+ $self->command = $process->getCommand();
+ $self->running = $process->isRunning();
+ if ($memory = Memory::getUsageForPid($self->pid)) {
+ $self->memory = $memory;
+ }
+
+ return $self;
+ }
+
+ public static function fromSerialization($any)
+ {
+ $self = new static();
+ $self->pid = $any->pid;
+ $self->command = $any->command;
+ $self->running = $any->running;
+ $self->memory = $any->memory;
+
+ return $self;
+ }
+
+ /**
+ * @return int|null
+ */
+ public function getPid()
+ {
+ return $this->pid;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCommand()
+ {
+ return $this->command;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRunning()
+ {
+ return $this->running;
+ }
+
+ /**
+ * @return object|null
+ */
+ public function getMemory()
+ {
+ return $this->memory;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return (object) [
+ 'pid' => $this->pid,
+ 'command' => $this->command,
+ 'running' => $this->running,
+ 'memory' => $this->memory,
+ ];
+ }
+}
diff --git a/vendor/gipfl/process/src/ProcessKiller.php b/vendor/gipfl/process/src/ProcessKiller.php
new file mode 100644
index 0000000..cf904ce
--- /dev/null
+++ b/vendor/gipfl/process/src/ProcessKiller.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace gipfl\Process;
+
+use React\ChildProcess\Process as ChildProcess;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use function React\Promise\resolve;
+
+class ProcessKiller
+{
+ /**
+ * @param ChildProcess $process
+ * @param LoopInterface $loop
+ * @param int $timeout
+ * @return \React\Promise\ExtendedPromiseInterface
+ */
+ public static function terminateProcess(ChildProcess $process, LoopInterface $loop, $timeout = 0)
+ {
+ $processes = new ProcessList();
+ $processes->attach($process);
+ return static::terminateProcesses($processes, $loop, $timeout);
+ }
+
+ /**
+ * @param ProcessList $processes
+ * @param LoopInterface $loop
+ * @param int $timeout
+ * @return \React\Promise\ExtendedPromiseInterface
+ */
+ public static function terminateProcesses(ProcessList $processes, LoopInterface $loop, $timeout = 5)
+ {
+ if ($processes->count() === 0) {
+ return resolve();
+ }
+ $deferred = new Deferred();
+ $killTimer = $loop->addTimer($timeout, function () use ($deferred, $processes, $loop) {
+ /** @var ChildProcess $process */
+ foreach ($processes as $process) {
+ $pid = $process->getPid();
+ // Logger::error("Process $pid is still running, sending SIGKILL");
+ $process->terminate(SIGKILL);
+ }
+
+ // Let's add a bit of a delay after KILLing
+ $loop->addTimer(0.1, function () use ($deferred) {
+ $deferred->resolve();
+ });
+ });
+
+ $timer = $loop->addPeriodicTimer($timeout / 20, function () use (
+ $deferred,
+ $processes,
+ $loop,
+ &$timer,
+ $killTimer
+ ) {
+ $stopped = [];
+ /** @var ChildProcess $process */
+ foreach ($processes as $process) {
+ if (! $process->isRunning()) {
+ $stopped[] = $process;
+ }
+ }
+ foreach ($stopped as $process) {
+ $processes->detach($process);
+ }
+ if ($processes->count() === 0) {
+ $loop->cancelTimer($timer);
+ $loop->cancelTimer($killTimer);
+ $deferred->resolve();
+ }
+ });
+ /** @var ChildProcess $process */
+ foreach ($processes as $process) {
+ $process->terminate(SIGTERM);
+ }
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/gipfl/process/src/ProcessList.php b/vendor/gipfl/process/src/ProcessList.php
new file mode 100644
index 0000000..a3f7c30
--- /dev/null
+++ b/vendor/gipfl/process/src/ProcessList.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\Process;
+
+/**
+ * @method ChildProcess current
+ */
+use Evenement\EventEmitterInterface;
+use Evenement\EventEmitterTrait;
+use InvalidArgumentException;
+use React\ChildProcess\Process as ChildProcess;
+use SplObjectStorage;
+
+class ProcessList extends SplObjectStorage implements EventEmitterInterface
+{
+ const ON_ATTACHED = 'attached';
+ const ON_DETACHED = 'detached';
+
+ use EventEmitterTrait;
+
+ #[\ReturnTypeWillChange]
+ public function attach($object, $info = null)
+ {
+ if (! $object instanceof ChildProcess || $info !== null) {
+ throw new InvalidArgumentException(sprintf(
+ 'Can attach only %s instances',
+ ChildProcess::class
+ ));
+ }
+ $object->on('exit', function () use ($object) {
+ $this->detach($object);
+ });
+
+ parent::attach($object, $info);
+ $this->emit(self::ON_ATTACHED, [$object]);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function detach($object)
+ {
+ parent::detach($object);
+ $this->emit(self::ON_DETACHED, [$object]);
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/LICENSE b/vendor/gipfl/protocol-jsonrpc/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/protocol-jsonrpc/composer.json b/vendor/gipfl/protocol-jsonrpc/composer.json
new file mode 100644
index 0000000..4a202eb
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "gipfl/protocol-jsonrpc",
+ "description": "JsonRPC Connection implementation",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Protocol\\JsonRpc\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ext-json": "*",
+ "gipfl/json": ">=0.1",
+ "gipfl/openrpc": "^0.2.1",
+ "gipfl/protocol": ">=0.2",
+ "psr/log": ">=1.1",
+ "react/promise": ">=2.7",
+ "react/stream": ">=1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^7.5 || ^6.5 || ^5.7",
+ "squizlabs/php_codesniffer": "^3.6"
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Connection.php b/vendor/gipfl/protocol-jsonrpc/src/Connection.php
new file mode 100644
index 0000000..be4b33f
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Connection.php
@@ -0,0 +1,310 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+use Evenement\EventEmitterTrait;
+use Exception;
+use gipfl\Json\JsonEncodeException;
+use InvalidArgumentException;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use React\Promise\Deferred;
+use React\Promise\Promise;
+use React\Stream\DuplexStreamInterface;
+use React\Stream\Util;
+use RuntimeException;
+use function call_user_func_array;
+use function is_object;
+use function mt_rand;
+use function preg_quote;
+use function preg_split;
+use function React\Promise\reject;
+use function sprintf;
+
+/**
+ * @deprecated Please use JsonRpcConection
+ */
+class Connection implements LoggerAwareInterface
+{
+ use EventEmitterTrait;
+ use LoggerAwareTrait;
+
+ /** @var DuplexStreamInterface */
+ protected $connection;
+
+ /** @var array */
+ protected $handlers = [];
+
+ /** @var Deferred[] */
+ protected $pending = [];
+
+ protected $nsSeparator = '.';
+
+ protected $nsRegex = '/\./';
+
+ protected $unknownErrorCount = 0;
+
+ public function handle(DuplexStreamInterface $connection)
+ {
+ $this->connection = $connection;
+ $this->connection->on('data', function ($data) {
+ try {
+ $this->handlePacket(Packet::decode($data));
+ } catch (Exception $error) {
+ echo $error->getMessage() . "\n";
+ $this->unknownErrorCount++;
+ if ($this->unknownErrorCount === 3) {
+ $this->close();
+ }
+ $response = new Response();
+ $response->setError(Error::forException($error));
+ $this->connection->write($response->toString());
+ }
+ });
+ $connection->on('close', function () {
+ $this->rejectAllPendingRequests('Connection closed');
+ });
+ // TODO: figure out whether and how to deal with the pipe event
+ Util::forwardEvents($connection, $this, ['end', 'error', 'close', 'drain']);
+ }
+
+ public function setNamespaceSeparator($separator)
+ {
+ $this->nsSeparator = $separator;
+ $this->nsRegex = '/' . preg_quote($separator, '/') . '/';
+
+ return $this;
+ }
+
+ /**
+ * @param Packet $packet
+ */
+ protected function handlePacket(Packet $packet)
+ {
+ if ($packet instanceof Response) {
+ $this->handleResponse($packet);
+ } elseif ($packet instanceof Request) {
+ $this->handleRequest($packet);
+ } elseif ($packet instanceof Notification) {
+ $this->handleNotification($packet);
+ } else {
+ // Will not happen as long as there is no bug in Packet
+ throw new RuntimeException('Packet was neither Request/Notification nor Response');
+ }
+ }
+
+ protected function handleResponse(Response $response)
+ {
+ $id = $response->getId();
+ if (isset($this->pending[$id])) {
+ $promise = $this->pending[$id];
+ unset($this->pending[$id]);
+ $promise->resolve($response);
+ } else {
+ $this->handleUnmatchedResponse($response);
+ }
+ }
+
+ protected function handleUnmatchedResponse(Response $response)
+ {
+ // Ignore. Log?
+ }
+
+ protected function handleRequest(Request $request)
+ {
+ $result = $this->handleNotification($request);
+ $this->sendResultForRequest($request, $result);
+ }
+
+ protected function sendResultForRequest(Request $request, $result)
+ {
+ if ($result instanceof Error) {
+ $response = Response::forRequest($request);
+ $response->setError($result);
+
+ $this->connection->write($response->toString());
+ } elseif ($result instanceof Promise) {
+ $result->then(function ($result) use ($request) {
+ $this->sendResultForRequest($request, $result);
+ })->otherwise(function ($error) use ($request) {
+ $response = Response::forRequest($request);
+ if ($error instanceof Exception) {
+ $response->setError(Error::forException($error));
+ } else {
+ $response->setError(new Error(Error::INTERNAL_ERROR, $error));
+ }
+ // TODO: Double-check, this used to loop
+ $this->connection->write($response->toString());
+ });
+ } else {
+ $response = Response::forRequest($request);
+ $response->setResult($result);
+ $this->connection->write($response->toString());
+ }
+ }
+
+ /**
+ * @param Notification $notification
+ * @return Error|mixed
+ */
+ protected function handleNotification(Notification $notification)
+ {
+ $method = $notification->getMethod();
+ if (\strpos($method, $this->nsSeparator) === false) {
+ $namespace = null;
+ } else {
+ list($namespace, $method) = preg_split($this->nsRegex, $method, 2);
+ }
+
+ try {
+ $response = $this->call($namespace, $method, $notification);
+
+ return $response;
+ } catch (Exception $exception) {
+ return Error::forException($exception);
+ }
+ }
+
+ /**
+ * @param Request $request
+ * @return \React\Promise\PromiseInterface
+ */
+ public function sendRequest(Request $request)
+ {
+ $id = $request->getId();
+ if ($id === null) {
+ $id = $this->getNextRequestId();
+ $request->setId($id);
+ }
+ if (isset($this->pending[$id])) {
+ throw new InvalidArgumentException(
+ "A request with id '$id' is already pending"
+ );
+ }
+ if (!$this->connection->isWritable()) {
+ return reject(new Exception('Cannot write to socket'));
+ }
+ try {
+ $this->connection->write($request->toString());
+ } catch (JsonEncodeException $e) {
+ return reject($e->getMessage());
+ }
+ $deferred = new Deferred();
+ $this->pending[$id] = $deferred;
+
+ return $deferred->promise()->then(function (Response $response) use ($deferred) {
+ if ($response->isError()) {
+ $deferred->reject(new RuntimeException($response->getError()->getMessage()));
+ } else {
+ $deferred->resolve($response->getResult());
+ }
+ }, function (Exception $e) use ($deferred) {
+ $deferred->reject($e);
+ });
+ }
+
+ public function request($method, $params = null)
+ {
+ return $this->sendRequest(new Request($method, $this->getNextRequestId(), $params));
+ }
+
+ protected function getNextRequestId()
+ {
+ for ($i = 0; $i < 100; $i++) {
+ $id = mt_rand(1, 1000000000);
+ if (!isset($this->pending[$id])) {
+ return $id;
+ }
+ }
+
+ throw new RuntimeException('Unable to generate a free random request ID, gave up after 100 attempts');
+ }
+
+ /**
+ * @param Request|mixed $request
+ */
+ public function forgetRequest($request)
+ {
+ if ($request instanceof Request) {
+ unset($this->pending[$request->getId()]);
+ } else {
+ unset($this->pending[$request]);
+ }
+ }
+
+ /**
+ * @param Notification $packet
+ */
+ public function sendNotification(Notification $packet)
+ {
+ $this->connection->write($packet->toString());
+ }
+
+ /**
+ * @param string $method
+ * @param null $params
+ */
+ public function notification($method, $params = null)
+ {
+ $notification = new Notification($method, $params);
+ $this->sendNotification($notification);
+ }
+
+ /**
+ * @param $namespace
+ * @param $handler
+ * @return Connection
+ */
+ public function setHandler($handler, $namespace = null)
+ {
+ $this->handlers[$namespace] = $handler;
+
+ return $this;
+ }
+
+ protected function call($namespace, $method, Notification $packet)
+ {
+ if (isset($this->handlers[$namespace])) {
+ $handler = $this->handlers[$namespace];
+ if ($handler instanceof PacketHandler) {
+ return $handler->handle($packet);
+ }
+
+ // Legacy handlers, deprecated:
+ $params = $packet->getParams();
+ if (is_object($params)) {
+ return $handler->$method($params);
+ }
+
+ return call_user_func_array([$handler, $method], $params);
+ }
+
+ $error = new Error(Error::METHOD_NOT_FOUND);
+ $error->setMessage(sprintf(
+ '%s: %s%s%s',
+ $error->getMessage(),
+ $namespace,
+ $this->nsSeparator,
+ $method
+ ));
+
+ return $error;
+ }
+
+ protected function rejectAllPendingRequests($message)
+ {
+ foreach ($this->pending as $pending) {
+ $pending->reject(new Exception());
+ }
+ $this->pending = [];
+ }
+
+ public function close()
+ {
+ if ($this->connection) {
+ $this->connection->close();
+ $this->handlers = [];
+ $this->connection = null;
+ }
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Error.php b/vendor/gipfl/protocol-jsonrpc/src/Error.php
new file mode 100644
index 0000000..dc1d639
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Error.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+use Exception;
+use JsonSerializable;
+use TypeError;
+
+class Error implements JsonSerializable
+{
+ const PARSE_ERROR = -32700;
+
+ const INVALID_REQUEST = -32600;
+
+ const METHOD_NOT_FOUND = -32601;
+
+ const INVALID_PARAMS = 32602;
+
+ const INTERNAL_ERROR = 32603;
+
+ // Reserved for implementation-defined server-errors:
+ const MIN_CUSTOM_ERROR = -32000;
+
+ const MAX_CUSTOM_ERROR = -32099;
+
+ protected static $wellKnownErrorCodes = [
+ self::PARSE_ERROR,
+ self::INVALID_REQUEST,
+ self::METHOD_NOT_FOUND,
+ self::INVALID_PARAMS,
+ self::INTERNAL_ERROR,
+ ];
+
+ protected static $errorMessages = [
+ self::PARSE_ERROR => 'Invalid JSON was received by the server.',
+ self::INVALID_REQUEST => 'The JSON sent is not a valid Request object',
+ self::METHOD_NOT_FOUND => 'The method does not exist / is not available',
+ self::INVALID_PARAMS => 'Invalid method parameter(s)',
+ self::INTERNAL_ERROR => 'Internal JSON-RPC error',
+ ];
+
+ protected static $defaultCustomMessage = 'Server error. Reserved for implementation-defined server-errors.';
+
+ /** @var int */
+ protected $code;
+
+ /** @var string */
+ protected $message;
+
+ /** @var mixed|null */
+ protected $data;
+
+ /**
+ * Error constructor.
+ * @param int $code
+ * @param string $message
+ * @param mixed $data
+ */
+ public function __construct($code, $message = null, $data = null)
+ {
+ if ($message === null) {
+ if ($this->isCustomErrorCode($code)) {
+ $message = self::$defaultCustomMessage;
+ } elseif (static::isWellKnownErrorCode($code)) {
+ $message = self::$errorMessages[$code];
+ } else {
+ $message = 'Unknown error';
+ }
+ }
+ $this->code = $code;
+ $this->message = $message;
+ $this->data = $data;
+ }
+
+ public static function forException(Exception $exception)
+ {
+ $code = $exception->getCode();
+ if (! static::isCustomErrorCode($code)
+ && ! static::isWellKnownErrorCode($code)
+ ) {
+ $code = self::INTERNAL_ERROR;
+ }
+ if (static::isWellKnownErrorCode($code) && $code !== self::INTERNAL_ERROR) {
+ $data = null;
+ } else {
+ $data = $exception->getTraceAsString();
+ }
+ if (function_exists('iconv')) {
+ $data = iconv('UTF-8', 'UTF-8//IGNORE', $data);
+ }
+
+ return new Error($code, sprintf(
+ '%s in %s(%d)',
+ $exception->getMessage(),
+ $exception->getFile(),
+ $exception->getLine()
+ ), $data);
+ }
+
+ public static function forTypeError(TypeError $error)
+ {
+ $code = self::INVALID_PARAMS;
+
+ return new Error($code, sprintf(
+ '%s in %s(%d)',
+ $error->getMessage(),
+ $error->getFile(),
+ $error->getLine()
+ ));
+ }
+
+ /**
+ * @return int
+ */
+ public function getCode()
+ {
+ return $this->code;
+ }
+
+ /**
+ * @param int $code
+ * @return $this
+ */
+ public function setCode($code)
+ {
+ $this->code = $code;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * @param string $message
+ * @return $this
+ */
+ public function setMessage($message)
+ {
+ $this->message = $message;
+ return $this;
+ }
+
+ /**
+ * @return mixed|null
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * @param mixed|null $data
+ * @return $this
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+ return $this;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $result = [
+ 'code' => $this->code,
+ 'message' => $this->message,
+ ];
+
+ if ($this->data !== null) {
+ $result['data'] = $this->data;
+ }
+
+ return (object) $result;
+ }
+
+ public static function isWellKnownErrorCode($code)
+ {
+ return isset(self::$errorMessages[$code]);
+ }
+
+ public static function isCustomErrorCode($code)
+ {
+ return $code >= self::MIN_CUSTOM_ERROR && $code <= self::MAX_CUSTOM_ERROR;
+ }
+
+ /**
+ * @deprecated please use jsonSerialize()
+ * @return mixed
+ */
+ public function toPlainObject()
+ {
+ return $this->jsonSerialize();
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Handler/FailingPacketHandler.php b/vendor/gipfl/protocol-jsonrpc/src/Handler/FailingPacketHandler.php
new file mode 100644
index 0000000..0c04ac7
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Handler/FailingPacketHandler.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc\Handler;
+
+use gipfl\Protocol\JsonRpc\Error;
+use gipfl\Protocol\JsonRpc\Notification;
+use gipfl\Protocol\JsonRpc\Request;
+
+class FailingPacketHandler implements JsonRpcHandler
+{
+ /** @var Error */
+ protected $error;
+
+ public function __construct(Error $error)
+ {
+ $this->error = $error;
+ }
+
+ public function processNotification(Notification $notification)
+ {
+ // We silently ignore them
+ }
+
+ public function processRequest(Request $request)
+ {
+ return $this->error;
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Handler/JsonRpcHandler.php b/vendor/gipfl/protocol-jsonrpc/src/Handler/JsonRpcHandler.php
new file mode 100644
index 0000000..f64bc68
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Handler/JsonRpcHandler.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc\Handler;
+
+use gipfl\Protocol\JsonRpc\Error;
+use gipfl\Protocol\JsonRpc\Notification;
+use gipfl\Protocol\JsonRpc\Request;
+use React\Promise\PromiseInterface;
+
+interface JsonRpcHandler
+{
+ /**
+ * @param Request $request
+ * @return Error|PromiseInterface|mixed
+ */
+ public function processRequest(Request $request);
+
+ /**
+ * @param Notification $notification
+ * @return void
+ */
+ public function processNotification(Notification $notification);
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Handler/NamespacedPacketHandler.php b/vendor/gipfl/protocol-jsonrpc/src/Handler/NamespacedPacketHandler.php
new file mode 100644
index 0000000..6e0655b
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Handler/NamespacedPacketHandler.php
@@ -0,0 +1,217 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc\Handler;
+
+use Exception;
+use gipfl\Json\JsonSerialization;
+use gipfl\OpenRpc\Reflection\MetaDataClass;
+use gipfl\OpenRpc\Reflection\MetaDataMethod;
+use gipfl\Protocol\JsonRpc\Error;
+use gipfl\Protocol\JsonRpc\Notification;
+use gipfl\Protocol\JsonRpc\Request;
+use RuntimeException;
+use TypeError;
+use function call_user_func_array;
+use function method_exists;
+use function preg_split;
+use function sprintf;
+use function strpos;
+
+class NamespacedPacketHandler implements JsonRpcHandler
+{
+ protected $nsSeparator = '.';
+
+ protected $nsRegex = '/\./';
+
+ protected $handlers = [];
+
+ /**
+ * @var MetaDataMethod[]
+ */
+ protected $knownMethods = [];
+
+ public function processNotification(Notification $notification)
+ {
+ list($namespace, $method) = $this->splitMethod($notification->getMethod());
+ try {
+ $this->call($namespace, $method, $notification);
+ } catch (Exception $exception) {
+ // Well... we might want to log this
+ } catch (TypeError $exception) {
+ // Well... we might want to log this
+ }
+ }
+
+ public function processRequest(Request $request)
+ {
+ list($namespace, $method) = $this->splitMethod($request->getMethod());
+
+ try {
+ return $this->call($namespace, $method, $request);
+ } catch (Exception $exception) {
+ return Error::forException($exception);
+ } catch (TypeError $error) {
+ return Error::forTypeError($error);
+ }
+ }
+
+ /**
+ * @param string $namespace
+ * @param object $handler
+ */
+ public function registerNamespace($namespace, $handler)
+ {
+ if (isset($this->handlers[$namespace])) {
+ throw new RuntimeException("Cannot register a namespace twice: '$namespace'");
+ }
+ $this->handlers[$namespace] = $handler;
+ $this->analyzeNamespace($namespace, $handler);
+ }
+
+ protected function analyzeNamespace($namespace, $handler)
+ {
+ $meta = MetaDataClass::analyze(get_class($handler));
+ foreach ($meta->getMethods() as $method) {
+ $this->knownMethods[$namespace . $this->nsSeparator . $method->getName()] = $method;
+ }
+ }
+
+ /**
+ * @param string $namespace
+ */
+ public function removeNamespace($namespace)
+ {
+ unset($this->handlers[$namespace]);
+ }
+
+ public function setNamespaceSeparator($separator)
+ {
+ $this->nsSeparator = $separator;
+ $this->nsRegex = '/' . preg_quote($separator, '/') . '/';
+
+ return $this;
+ }
+
+ protected function call($namespace, $method, Notification $notification)
+ {
+ if (! isset($this->handlers[$namespace])) {
+ return $this->notFound($notification, ', no handler for ' . $namespace);
+ }
+
+ $handler = $this->handlers[$namespace];
+ if ($handler instanceof JsonRpcHandler) {
+ if ($notification instanceof Request) {
+ return $handler->processRequest($notification);
+ } else {
+ $handler->processNotification($notification);
+ }
+ }
+
+ $params = $notification->getParams();
+ if (! is_array($params)) {
+ try {
+ $params = $this->prepareParams($notification->getMethod(), $params);
+ } catch (Exception $e) {
+ return Error::forException($e);
+ }
+ }
+ if ($notification instanceof Request) {
+ $rpcMethod = $method . 'Request';
+ if (is_callable([$handler, $rpcMethod])) {
+ return call_user_func_array([$handler, $rpcMethod], $params);
+ }
+
+ return $this->notFound($notification, ', no ' . $rpcMethod);
+ } else {
+ $rpcMethod = $method . 'Notification';
+ if (is_callable([$handler, $rpcMethod])) {
+ call_user_func_array([$handler, $rpcMethod], $params);
+ }
+
+ return null;
+ }
+ }
+
+ protected function prepareParams($method, $params)
+ {
+ if (! isset($this->knownMethods[$method])) {
+ throw new Exception('Cannot map params for unknown method');
+ }
+
+ $meta = $this->knownMethods[$method];
+ $result = [];
+ foreach ($meta->getParameters() as $parameter) {
+ $name = $parameter->getName();
+ if (property_exists($params, $name)) {
+ $value = $params->$name;
+ if ($value === null) {
+ // TODO: check if required
+ $result[] = $value;
+ continue;
+ }
+ switch ($parameter->getType()) {
+ case 'int':
+ $result[] = (int) $value;
+ break;
+ case 'float':
+ $result[] = (float) $value;
+ break;
+ case 'string':
+ $result[] = (string) $value;
+ break;
+ case 'array':
+ $result[] = (array) $value;
+ break;
+ case 'bool':
+ case 'boolean':
+ $result[] = (bool) $value;
+ break;
+ case 'object':
+ $result[] = (object) $value;
+ break;
+ default:
+ $type = $parameter->getType();
+ if (class_exists($type)) {
+ foreach (class_implements($type) as $implement) {
+ if ($implement === JsonSerialization::class) {
+ $result[] = $type::fromSerialization($value);
+ break 2;
+ }
+ }
+ }
+ throw new Exception(sprintf(
+ 'Unsupported parameter type for %s: %s',
+ $method,
+ $parameter->getType()
+ ));
+ }
+ } else {
+ // TODO: isRequired? Set null
+ throw new Exception("Missing parameter for $method: $name");
+ }
+ }
+
+ return $result;
+ }
+
+ protected function splitMethod($method)
+ {
+ if (strpos($method, $this->nsSeparator) === false) {
+ return [null, $method];
+ }
+
+ return preg_split($this->nsRegex, $method, 2);
+ }
+
+ protected function notFound(Notification $notification, $suffix = '')
+ {
+ $error = new Error(Error::METHOD_NOT_FOUND);
+ $error->setMessage(sprintf(
+ '%s: %s' . $suffix,
+ $error->getMessage(),
+ $notification->getMethod()
+ ));
+
+ return $error;
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/JsonRpcConnection.php b/vendor/gipfl/protocol-jsonrpc/src/JsonRpcConnection.php
new file mode 100644
index 0000000..88c6f5b
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/JsonRpcConnection.php
@@ -0,0 +1,241 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+use Evenement\EventEmitterTrait;
+use Exception;
+use gipfl\Json\JsonEncodeException;
+use gipfl\Protocol\JsonRpc\Handler\JsonRpcHandler;
+use InvalidArgumentException;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
+use React\Promise\Deferred;
+use React\Promise\Promise;
+use React\Stream\DuplexStreamInterface;
+use React\Stream\Util;
+use RuntimeException;
+use function mt_rand;
+use function React\Promise\reject;
+use function React\Promise\resolve;
+
+class JsonRpcConnection implements LoggerAwareInterface
+{
+ use EventEmitterTrait;
+ use LoggerAwareTrait;
+
+ /** @var DuplexStreamInterface */
+ protected $connection;
+
+ /** @var ?JsonRpcHandler */
+ protected $handler;
+
+ /** @var Deferred[] */
+ protected $pending = [];
+
+ protected $unknownErrorCount = 0;
+
+ public function __construct(DuplexStreamInterface $connection, JsonRpcHandler $handler = null)
+ {
+ $this->setLogger(new NullLogger());
+ $this->connection = $connection;
+ $this->setHandler($handler);
+ $this->connection->on('data', function ($data) {
+ try {
+ $this->handlePacket(Packet::decode($data));
+ } catch (\Exception $error) {
+ $this->logger->error($error->getMessage());
+ $this->unknownErrorCount++;
+ if ($this->unknownErrorCount === 3) {
+ // e.g.: decoding errors
+ // TODO: should we really close? Or just send error responses for every Exception?
+ $this->close();
+ }
+ $response = new Response();
+ $response->setError(Error::forException($error));
+ $this->connection->write($response->toString());
+ }
+ });
+ $connection->on('close', function () {
+ $this->rejectAllPendingRequests('Connection closed');
+ });
+ // Hint: Util::pipe takes care of the pipe event
+ Util::forwardEvents($connection, $this, ['end', 'error', 'close', 'drain']);
+ }
+
+ /**
+ * @param Packet $packet
+ */
+ protected function handlePacket(Packet $packet)
+ {
+ if ($packet instanceof Response) {
+ $this->handleResponse($packet);
+ } elseif ($packet instanceof Request) {
+ if ($this->handler) {
+ $result = $this->handler->processRequest($packet);
+ } else {
+ $result = new Error(Error::METHOD_NOT_FOUND);
+ $result->setMessage($result->getMessage() . ': ' . $packet->getMethod());
+ }
+ $this->sendResultForRequest($packet, $result);
+ } elseif ($packet instanceof Notification) {
+ if ($this->handler) {
+ $this->handler->processNotification($packet);
+ }
+ } else {
+ // Will not happen as long as there is no bug in Packet
+ throw new RuntimeException('Packet was neither Request/Notification nor Response');
+ }
+ }
+
+ protected function handleResponse(Response $response)
+ {
+ $id = $response->getId();
+ if (isset($this->pending[$id])) {
+ $promise = $this->pending[$id];
+ unset($this->pending[$id]);
+ $promise->resolve($response);
+ } else {
+ $this->handleUnmatchedResponse($response);
+ }
+ }
+
+ protected function handleUnmatchedResponse(Response $response)
+ {
+ $this->logger->error('Unmatched Response: ' . $response->toString());
+ }
+
+ protected function sendResultForRequest(Request $request, $result)
+ {
+ if ($result instanceof Error) {
+ $response = Response::forRequest($request);
+ $response->setError($result);
+ if ($this->connection && $this->connection->isWritable()) {
+ $this->connection->write($response->toString());
+ } else {
+ $this->logger->error('Failed to send response, have no writable connection');
+ }
+ } elseif ($result instanceof Promise) {
+ $result->then(function ($result) use ($request) {
+ $this->sendResultForRequest($request, $result);
+ }, function ($error) use ($request) {
+ $response = Response::forRequest($request);
+ if ($error instanceof Exception || $error instanceof \Throwable) {
+ $response->setError(Error::forException($error));
+ } else {
+ $response->setError(new Error(Error::INTERNAL_ERROR, $error));
+ }
+ // TODO: Double-check, this used to loop
+ $this->connection->write($response->toString());
+ })->done();
+ } else {
+ $response = Response::forRequest($request);
+ $response->setResult($result);
+ if ($this->connection && $this->connection->isWritable()) {
+ $this->connection->write($response->toString());
+ } else {
+ $this->logger->error('Failed to send response, have no writable connection');
+ }
+ }
+ }
+
+ /**
+ * @param Request $request
+ * @return \React\Promise\PromiseInterface
+ */
+ public function sendRequest(Request $request)
+ {
+ $id = $request->getId();
+ if ($id === null) {
+ $id = $this->getNextRequestId();
+ $request->setId($id);
+ }
+ if (isset($this->pending[$id])) {
+ throw new InvalidArgumentException(
+ "A request with id '$id' is already pending"
+ );
+ }
+ if (!$this->connection->isWritable()) {
+ return reject(new Exception('Cannot write to socket'));
+ }
+ try {
+ $this->connection->write($request->toString());
+ } catch (JsonEncodeException $e) {
+ return reject($e->getMessage());
+ }
+ $deferred = new Deferred(function () use ($id) {
+ unset($this->pending[$id]);
+ });
+ $this->pending[$id] = $deferred;
+
+ return $deferred->promise()->then(function (Response $response) {
+ if ($response->isError()) {
+ return reject(new RuntimeException($response->getError()->getMessage()));
+ }
+
+ return resolve($response->getResult());
+ });
+ }
+
+ public function request($method, $params = null)
+ {
+ return $this->sendRequest(new Request($method, $this->getNextRequestId(), $params));
+ }
+
+ protected function getNextRequestId()
+ {
+ for ($i = 0; $i < 100; $i++) {
+ $id = mt_rand(1, 1000000000);
+ if (!isset($this->pending[$id])) {
+ return $id;
+ }
+ }
+
+ throw new RuntimeException('Unable to generate a free random request ID, gave up after 100 attempts');
+ }
+
+ /**
+ * @param Notification $packet
+ */
+ public function sendNotification(Notification $packet)
+ {
+ $this->connection->write($packet->toString());
+ }
+
+ /**
+ * @param string $method
+ * @param null $params
+ */
+ public function notification($method, $params = null)
+ {
+ $notification = new Notification($method, $params);
+ $this->sendNotification($notification);
+ }
+
+ /**
+ * @param PacketHandler $handler
+ * @return $this
+ */
+ public function setHandler(JsonRpcHandler $handler = null)
+ {
+ $this->handler = $handler;
+ return $this;
+ }
+
+ protected function rejectAllPendingRequests($message)
+ {
+ foreach ($this->pending as $pending) {
+ $pending->reject(new Exception($message));
+ }
+ $this->pending = [];
+ }
+
+ public function close()
+ {
+ if ($this->connection) {
+ $this->connection->close();
+ $this->handler = null;
+ $this->connection = null;
+ }
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Notification.php b/vendor/gipfl/protocol-jsonrpc/src/Notification.php
new file mode 100644
index 0000000..338efc1
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Notification.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+class Notification extends Packet
+{
+ /** @var string */
+ protected $method;
+
+ /** @var \stdClass|array */
+ protected $params;
+
+ public function __construct($method, $params)
+ {
+ $this->setMethod($method);
+ $this->setParams($params);
+ }
+
+ /**
+ * @return string
+ */
+ public function getMethod()
+ {
+ return $this->method;
+ }
+
+ /**
+ * @param string $method
+ */
+ public function setMethod($method)
+ {
+ $this->method = $method;
+ }
+
+ /**
+ * @return object|array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * @param object|array $params
+ */
+ public function setParams($params)
+ {
+ $this->params = $params;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $default
+ * @return mixed|null
+ */
+ public function getParam($name, $default = null)
+ {
+ $p = & $this->params;
+ if (\is_object($p) && \property_exists($p, $name)) {
+ return $p->$name;
+ } elseif (\is_array($p) && \array_key_exists($name, $p)) {
+ return $p[$name];
+ }
+
+ return $default;
+ }
+
+ /**
+ * @return object
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $plain = [
+ 'jsonrpc' => '2.0',
+ 'method' => $this->method,
+ 'params' => $this->params,
+ ];
+
+ if ($this->hasExtraProperties()) {
+ $plain += (array) $this->getExtraProperties();
+ }
+
+ return (object) $plain;
+ }
+
+ /**
+ * @param $method
+ * @param $params
+ * @return static
+ */
+ public static function create($method, $params)
+ {
+ $packet = new Notification($method, $params);
+
+ return $packet;
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Packet.php b/vendor/gipfl/protocol-jsonrpc/src/Packet.php
new file mode 100644
index 0000000..8dca44e
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Packet.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+use gipfl\Json\JsonException;
+use gipfl\Json\JsonSerialization;
+use gipfl\Json\JsonString;
+use gipfl\Protocol\Exception\ProtocolError;
+use function property_exists;
+
+abstract class Packet implements JsonSerialization
+{
+ /** @var \stdClass|null */
+ protected $extraProperties;
+
+ /**
+ * @return string
+ * @throws \gipfl\Json\JsonEncodeException
+ */
+ public function toString()
+ {
+ return JsonString::encode($this->jsonSerialize());
+ }
+
+ /**
+ * @return string
+ * @throws \gipfl\Json\JsonEncodeException
+ */
+ public function toPrettyString()
+ {
+ return JsonString::encode($this->jsonSerialize(), JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasExtraProperties()
+ {
+ return $this->extraProperties !== null;
+ }
+
+ /**
+ * @return \stdClass|null
+ */
+ public function getExtraProperties()
+ {
+ return $this->extraProperties;
+ }
+
+ /**
+ * @param \stdClass|null $extraProperties
+ * @return $this
+ * @throws ProtocolError
+ */
+ public function setExtraProperties($extraProperties)
+ {
+ foreach (['id', 'error', 'result', 'jsonrpc', 'method', 'params'] as $key) {
+ if (property_exists($extraProperties, $key)) {
+ throw new ProtocolError("Cannot accept '$key' as an extra property");
+ }
+ }
+ $this->extraProperties = $extraProperties;
+
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed|null $default
+ * @return mixed|null
+ */
+ public function getExtraProperty($name, $default = null)
+ {
+ if (isset($this->extraProperties->$name)) {
+ return $this->extraProperties->$name;
+ } else {
+ return $default;
+ }
+ }
+
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @return $this
+ */
+ public function setExtraProperty($name, $value)
+ {
+ if ($this->extraProperties === null) {
+ $this->extraProperties = (object) [$name => $value];
+ } else {
+ $this->extraProperties->$name = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $string
+ * @return Notification|Request|Response
+ * @throws ProtocolError
+ */
+ public static function decode($string)
+ {
+ try {
+ return self::fromSerialization(JsonString::decode($string));
+ } catch (JsonException $e) {
+ throw new ProtocolError(sprintf(
+ 'JSON decode failed: %s',
+ $e->getMessage()
+ ), Error::PARSE_ERROR);
+ }
+ }
+
+ public static function fromSerialization($any)
+ {
+ $version = static::stripRequiredProperty($any, 'jsonrpc');
+ if ($version !== '2.0') {
+ throw new ProtocolError(
+ "Only JSON-RPC 2.0 is supported, got $version",
+ Error::INVALID_REQUEST
+ );
+ }
+
+ // Hint: we MUST use property_exists here, as a NULL id is allowed
+ // in error responsed in case it wasn't possible to determine a
+ // request id
+ $hasId = property_exists($any, 'id');
+ $id = static::stripOptionalProperty($any, 'id');
+ $error = static::stripOptionalProperty($any, 'error');
+ if (property_exists($any, 'method')) {
+ $method = static::stripRequiredProperty($any, 'method');
+ $params = static::stripRequiredProperty($any, 'params');
+
+ if ($id === null) {
+ $packet = new Notification($method, $params);
+ } else {
+ $packet = new Request($method, $id, $params);
+ }
+ } elseif (! $hasId) {
+ throw new ProtocolError(
+ "Given string is not a valid JSON-RPC 2.0 response: id is missing",
+ Error::INVALID_REQUEST
+ );
+ } else {
+ $packet = new Response($id);
+ if ($error) {
+ $packet->setError(new Error(
+ static::stripOptionalProperty($error, 'code'),
+ static::stripOptionalProperty($error, 'message'),
+ static::stripOptionalProperty($error, 'data')
+ ));
+ } else {
+ $result = static::stripRequiredProperty($any, 'result');
+ $packet->setResult($result);
+ }
+ }
+ if (count((array) $any) > 0) {
+ $packet->setExtraProperties($any);
+ }
+
+ return $packet;
+ }
+
+ /**
+ * @param object $object
+ * @param string $property
+ * @throws ProtocolError
+ */
+ protected static function assertPropertyExists($object, $property)
+ {
+ if (! property_exists($object, $property)) {
+ throw new ProtocolError(
+ "Expected valid JSON-RPC, got no '$property' property",
+ Error::INVALID_REQUEST
+ );
+ }
+ }
+
+ /**
+ * @param \stdClass $object
+ * @param string $property
+ * @return mixed|null
+ */
+ protected static function stripOptionalProperty($object, $property)
+ {
+ if (property_exists($object, $property)) {
+ $value = $object->$property;
+ unset($object->$property);
+
+ return $value;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param \stdClass $object
+ * @param string $property
+ * @return mixed
+ * @throws ProtocolError
+ */
+ protected static function stripRequiredProperty($object, $property)
+ {
+ if (! property_exists($object, $property)) {
+ throw new ProtocolError(
+ "Expected valid JSON-RPC, got no '$property' property",
+ Error::INVALID_REQUEST
+ );
+ }
+
+ $value = $object->$property;
+ unset($object->$property);
+
+ return $value;
+ }
+
+ /**
+ * @deprecated please use jsonSerialize()
+ * @return string
+ */
+ public function toPlainObject()
+ {
+ return $this->jsonSerialize();
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/PacketHandler.php b/vendor/gipfl/protocol-jsonrpc/src/PacketHandler.php
new file mode 100644
index 0000000..e3f23c2
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/PacketHandler.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+/**
+ * @deprecated
+ */
+interface PacketHandler
+{
+ public function handle(Notification $notification);
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Request.php b/vendor/gipfl/protocol-jsonrpc/src/Request.php
new file mode 100644
index 0000000..2061a41
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Request.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+use gipfl\Protocol\Exception\ProtocolError;
+
+class Request extends Notification
+{
+ /** @var mixed */
+ protected $id;
+
+ /**
+ * Request constructor.
+ * @param string $method
+ * @param mixed $id
+ * @param null $params
+ */
+ public function __construct($method, $id = null, $params = null)
+ {
+ parent::__construct($method, $params);
+
+ $this->id = $id;
+ }
+
+ /**
+ * @return object
+ * @throws ProtocolError
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ if ($this->id === null) {
+ throw new ProtocolError(
+ 'A request without an ID is not valid'
+ );
+ }
+
+ $plain = parent::jsonSerialize();
+ $plain->id = $this->id;
+
+ return $plain;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param mixed $id
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/Response.php b/vendor/gipfl/protocol-jsonrpc/src/Response.php
new file mode 100644
index 0000000..3a5ad90
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/Response.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+class Response extends Packet
+{
+ /** @var mixed|null This could be null when sending a parse error */
+ protected $id;
+
+ /** @var mixed */
+ protected $result;
+
+ /** @var Error|null */
+ protected $error;
+
+ /** @var string */
+ protected $message;
+
+ public function __construct($id = null)
+ {
+ $this->id = $id;
+ }
+
+ /**
+ * @param Request $request
+ * @return Response
+ */
+ public static function forRequest(Request $request)
+ {
+ return new Response($request->getId());
+ }
+
+ /**
+ * @return object
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $plain = [
+ 'jsonrpc' => '2.0',
+ ];
+ if ($this->hasExtraProperties()) {
+ $plain += (array) $this->getExtraProperties();
+ }
+
+ if ($this->id !== null) {
+ $plain['id'] = $this->id;
+ }
+
+ if ($this->error === null) {
+ $plain['result'] = $this->result;
+ } else {
+ if (! isset($plain['id'])) {
+ $plain['id'] = null;
+ }
+ $plain['error'] = $this->error;
+ }
+
+ return (object) $plain;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * @param $result
+ * @return $this
+ */
+ public function setResult($result)
+ {
+ $this->result = $result;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasId()
+ {
+ return null !== $this->id;
+ }
+
+ /**
+ * @return null|int|string
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * @param $id
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+ }
+
+ public function isError()
+ {
+ return $this->error !== null;
+ }
+
+ /**
+ * @return Error|null
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * @param $error
+ * @return $this;
+ */
+ public function setError(Error $error)
+ {
+ $this->error = $error;
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/protocol-jsonrpc/src/TestCase.php b/vendor/gipfl/protocol-jsonrpc/src/TestCase.php
new file mode 100644
index 0000000..05f54ba
--- /dev/null
+++ b/vendor/gipfl/protocol-jsonrpc/src/TestCase.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\Protocol\JsonRpc;
+
+use PHPUnit\Framework\TestCase as BaseTestCase;
+use React\EventLoop\LoopInterface;
+
+class TestCase extends BaseTestCase
+{
+ protected $examples = [];
+
+ protected function parseExample($key)
+ {
+ return Packet::decode($this->examples[$key]);
+ }
+
+ protected function failAfterSeconds($seconds, LoopInterface $loop)
+ {
+ $loop->addTimer($seconds, function () use ($seconds) {
+ throw new \RuntimeException("Timed out after $seconds seconds");
+ });
+ }
+
+ protected function collectErrorsForNotices(&$errors)
+ {
+ \set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$errors) {
+ if (\error_reporting() === 0) { // @-operator in use
+ return false;
+ }
+ $errors[] = new \ErrorException($errstr, 0, $errno, $errfile, $errline);
+
+ return false; // Always continue with normal error processing
+ }, E_ALL | E_STRICT);
+
+ \error_reporting(E_ALL | E_STRICT);
+ }
+
+ protected function throwEventualErrors(array $errors)
+ {
+ foreach ($errors as $error) {
+ throw $error;
+ }
+ }
+}
diff --git a/vendor/gipfl/protocol-netstring/LICENSE b/vendor/gipfl/protocol-netstring/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/protocol-netstring/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/protocol-netstring/composer.json b/vendor/gipfl/protocol-netstring/composer.json
new file mode 100644
index 0000000..0387e7d
--- /dev/null
+++ b/vendor/gipfl/protocol-netstring/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "gipfl/protocol-netstring",
+ "description": "Simple NetString stream wrapper",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Protocol\\NetString\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "ext-ctype": "*",
+ "gipfl/protocol": ">=0.2"
+ }
+}
diff --git a/vendor/gipfl/protocol-netstring/src/StreamWrapper.php b/vendor/gipfl/protocol-netstring/src/StreamWrapper.php
new file mode 100644
index 0000000..b23b909
--- /dev/null
+++ b/vendor/gipfl/protocol-netstring/src/StreamWrapper.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace gipfl\Protocol\NetString;
+
+use gipfl\Protocol\Exception\ProtocolError;
+use gipfl\Protocol\Generic\AbstractStreamWrapper;
+
+class StreamWrapper extends AbstractStreamWrapper
+{
+ protected $buffer = '';
+ protected $bufferLength = 0;
+ protected $bufferOffset = 0;
+ protected $expectedLength;
+
+ public function close()
+ {
+ // We might want to complain when buffer is not empty
+ $this->buffer = '';
+ $this->bufferLength = 0;
+ $this->bufferOffset = 0;
+ $this->expectedLength = null;
+ parent::close();
+ }
+
+ /**
+ * @param $data
+ */
+ public function handleData($data)
+ {
+ $this->buffer .= $data;
+ $this->bufferLength += \strlen($data);
+ while ($this->bufferHasPacket()) {
+ $this->processNextPacket();
+
+ if ($this->bufferOffset !== 0) {
+ $this->buffer = \substr($this->buffer, $this->bufferOffset);
+ $this->bufferOffset = 0;
+ $this->bufferLength = \strlen($this->buffer);
+ }
+ }
+ }
+
+ public function write($data)
+ {
+ return $this->output->write(strlen($data) . ':' . $data . ',');
+ }
+
+ public function end($data = null)
+ {
+ if ($data !== null) {
+ $this->write($data);
+ }
+
+ $this->output->end();
+ }
+
+ /**
+ * @return bool
+ */
+ protected function bufferHasPacket()
+ {
+ if ($this->expectedLength === null) {
+ if (false !== ($pos = \strpos(\substr($this->buffer, $this->bufferOffset, 10), ':'))) {
+ $lengthString = \ltrim(\substr($this->buffer, $this->bufferOffset, $pos), ',');
+ if (! \ctype_digit($lengthString)) {
+ $this->emit('error', [
+ new ProtocolError("Invalid length $lengthString")
+ ]);
+ $this->close();
+
+ return false;
+ }
+ $this->expectedLength = (int) $lengthString;
+ $this->bufferOffset = $pos + 1;
+ } elseif ($this->bufferLength > ($this->bufferOffset + 10)) {
+ $this->throwInvalidBuffer();
+ $this->close();
+
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ return $this->bufferLength > ($this->bufferOffset + $this->expectedLength);
+ }
+
+ protected function processNextPacket()
+ {
+ $packet = \substr($this->buffer, $this->bufferOffset, $this->expectedLength);
+
+ $this->bufferOffset = $this->bufferOffset + $this->expectedLength;
+ $this->expectedLength = null;
+
+ $this->emit('data', [$packet]);
+ }
+
+ protected function throwInvalidBuffer()
+ {
+ $len = \strlen($this->buffer);
+ if ($len < 200) {
+ $debug = $this->buffer;
+ } else {
+ $debug = \substr($this->buffer, 0, 100)
+ . \sprintf('[..] truncated %d bytes [..] ', $len)
+ . \substr($this->buffer, -100);
+ }
+
+ $this->emit('error', [
+ new ProtocolError("Got invalid NetString data: $debug")
+ ]);
+ }
+}
diff --git a/vendor/gipfl/protocol/LICENSE b/vendor/gipfl/protocol/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/protocol/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/protocol/composer.json b/vendor/gipfl/protocol/composer.json
new file mode 100644
index 0000000..4c204f3
--- /dev/null
+++ b/vendor/gipfl/protocol/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "gipfl/protocol",
+ "description": "Base library for some network protocol implementations",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Protocol\\Exception\\": "src/Exception",
+ "gipfl\\Protocol\\Generic\\": "src/Generic"
+ }
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "evenement/evenement": "^2",
+ "react/stream": "^1.0"
+ }
+}
diff --git a/vendor/gipfl/protocol/src/Exception/ProtocolError.php b/vendor/gipfl/protocol/src/Exception/ProtocolError.php
new file mode 100644
index 0000000..b4a42ca
--- /dev/null
+++ b/vendor/gipfl/protocol/src/Exception/ProtocolError.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace gipfl\Protocol\Exception;
+
+use Exception;
+
+class ProtocolError extends Exception
+{
+}
diff --git a/vendor/gipfl/protocol/src/Generic/AbstractStreamWrapper.php b/vendor/gipfl/protocol/src/Generic/AbstractStreamWrapper.php
new file mode 100644
index 0000000..12f6e82
--- /dev/null
+++ b/vendor/gipfl/protocol/src/Generic/AbstractStreamWrapper.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace gipfl\Protocol\Generic;
+
+use Evenement\EventEmitterTrait;
+use Exception;
+use React\Stream\DuplexStreamInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+use RuntimeException;
+
+abstract class AbstractStreamWrapper implements DuplexStreamInterface
+{
+ use EventEmitterTrait;
+
+ /** @var ReadableStreamInterface */
+ protected $input;
+
+ /** @var WritableStreamInterface */
+ protected $output;
+
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $in, WritableStreamInterface $out = null)
+ {
+ $this->readFrom($in);
+ if ($out === null && $in instanceof WritableStreamInterface) {
+ $this->writeTo($in);
+ } else {
+ $this->writeTo($out);
+ }
+ }
+
+ abstract public function handleData($data);
+
+ protected function readFrom(ReadableStreamInterface $input)
+ {
+ $this->input = $input;
+ if (! $input->isReadable()) {
+ $this->close();
+ return;
+ }
+ $input->on('data', function ($data) {
+ $this->handleData($data);
+ });
+ $input->on('end', function () {
+ $this->handleEnd();
+ });
+ $input->on('close', function () {
+ $this->close();
+ });
+ $input->on('error', function (Exception $error) {
+ $this->handleError($error);
+ });
+ }
+
+ protected function writeTo(WritableStreamInterface $output)
+ {
+ $this->output = $output;
+ if (! $this->output->isWritable()) {
+ $this->close();
+ throw new RuntimeException('Cannot write to output');
+ }
+
+ $output->on('drain', function () {
+ $this->handleDrain();
+ });
+ $output->on('close', function () {
+ $this->close();
+ });
+ $output->on('error', function (Exception $error) {
+ $this->handleError($error);
+ });
+ }
+
+ protected function handleDrain()
+ {
+ $this->emit('drain');
+ }
+
+ protected function handleEnd()
+ {
+ if (! $this->closed) {
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed && $this->input->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return !$this->closed && $this->output->isWritable();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->input->close();
+ $this->output->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ public function pause()
+ {
+ $this->input->pause();
+ }
+
+ public function resume()
+ {
+ $this->input->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = [])
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+
+ /**
+ * @param Exception $error
+ */
+ protected function handleError(Exception $error)
+ {
+ $this->emit('error', [$error]);
+ $this->close();
+ }
+}
diff --git a/vendor/gipfl/react-utils/LICENSE b/vendor/gipfl/react-utils/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/react-utils/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/react-utils/composer.json b/vendor/gipfl/react-utils/composer.json
new file mode 100644
index 0000000..638b7fe
--- /dev/null
+++ b/vendor/gipfl/react-utils/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "gipfl/react-utils",
+ "description": "Useful ReactPHP-related helper classes and methods",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\ReactUtils\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "gipfl/log": ">=0.1"
+ }
+}
diff --git a/vendor/gipfl/react-utils/src/RetryUnless.php b/vendor/gipfl/react-utils/src/RetryUnless.php
new file mode 100644
index 0000000..2d21123
--- /dev/null
+++ b/vendor/gipfl/react-utils/src/RetryUnless.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace gipfl\ReactUtils;
+
+use Exception;
+use gipfl\Log\DummyLogger;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+use RuntimeException;
+
+class RetryUnless implements LoggerAwareInterface
+{
+ use LoggerAwareTrait;
+
+ /** @var LoopInterface */
+ protected $loop;
+
+ /** @var Deferred */
+ protected $deferred;
+
+ /** @var TimerInterface */
+ protected $timer;
+
+ /** @var callable */
+ protected $callback;
+
+ /** @var bool */
+ protected $expectsSuccess;
+
+ /** @var int Regular interval */
+ protected $interval = 1;
+
+ /** @var int|null Optional, interval will be changed after $burst attempts */
+ protected $burst = null;
+
+ /** @var int|null Interval after $burst attempts */
+ protected $finalInterval = null;
+
+ /** @var int Current attempt count */
+ protected $attempts = 0;
+
+ /** @var bool No attempts will be made while paused */
+ protected $paused = false;
+
+ protected $lastError;
+
+ protected function __construct($callback, $expectsSuccess = true)
+ {
+ $this->setLogger(new DummyLogger());
+ $this->callback = $callback;
+ $this->expectsSuccess = $expectsSuccess;
+ }
+
+ public static function succeeding($callback)
+ {
+ return new static($callback);
+ }
+
+ public static function failing($callback)
+ {
+ return new static($callback, false);
+ }
+
+ public function run(LoopInterface $loop)
+ {
+ $this->assertNotRunning();
+ $this->deferred = $deferred = new Deferred();
+ $this->loop = $loop;
+ $loop->futureTick(function () {
+ $this->nextAttempt();
+ });
+
+ return $deferred->promise();
+ }
+
+ public function getLastError()
+ {
+ return $this->lastError;
+ }
+
+ public function setInterval($interval)
+ {
+ $this->interval = $interval;
+
+ return $this;
+ }
+
+ public function slowDownAfter($burst, $interval)
+ {
+ $this->burst = $burst;
+ $this->finalInterval = $interval;
+
+ return $this;
+ }
+
+ public function pause()
+ {
+ $this->removeEventualTimer();
+ $this->paused = true;
+
+ return $this;
+ }
+
+ public function resume()
+ {
+ if ($this->paused) {
+ $this->paused = false;
+ if ($this->timer === null) {
+ $this->nextAttempt();
+ }
+ }
+ }
+
+ public function reset()
+ {
+ $this->attempts = 0;
+ $this->paused = false;
+ $this->removeEventualTimer();
+ $this->rejectEventualDeferred('RetryUnless has been reset');
+
+ return $this;
+ }
+
+ public function getAttempts()
+ {
+ return $this->attempts;
+ }
+
+ protected function nextAttempt()
+ {
+ if ($this->paused) {
+ return;
+ }
+
+ $this->removeEventualTimer();
+ $this->attempts++;
+ try {
+ $callback = $this->callback;
+ $this->handleResult($callback());
+ } catch (Exception $e) {
+ $this->handleResult($e);
+ }
+ }
+
+ protected function logError(Exception $e)
+ {
+ if ($this->lastError !== $e->getMessage()) {
+ $this->lastError = $e->getMessage();
+ // TODO: Support exceptions in our logger?
+ $this->logger->error($e->getMessage());
+ }
+ }
+
+ protected function handleResult($result)
+ {
+ if ($this->expectsSuccess) {
+ if ($result instanceof Exception) {
+ $this->logError($result);
+ $this->scheduleNextAttempt();
+ } elseif ($result instanceof PromiseInterface) {
+ $later = function ($result) {
+ $this->handleResult($result);
+ };
+ $result->then($later, $later);
+ } else {
+ $this->succeed($result);
+ }
+ } else {
+ if ($result instanceof Exception) {
+ $this->succeed($result);
+ } else {
+ $this->scheduleNextAttempt();
+ }
+ }
+ }
+
+ protected function scheduleNextAttempt()
+ {
+ if ($this->timer !== null) {
+ throw new RuntimeException(
+ 'RetryUnless schedules next attempt while already scheduled'
+ );
+ }
+ $this->timer = $this->loop->addTimer($this->getNextInterval(), function () {
+ $this->nextAttempt();
+ });
+ }
+
+ protected function succeed($result)
+ {
+ $this->removeEventualTimer();
+ if ($this->deferred === null) {
+ $this->logger->warning('RetryUnless tries to resolve twice');
+
+ return;
+ }
+ $this->deferred->resolve($result);
+ $this->deferred = null;
+ $this->reset();
+ }
+
+ protected function getNextInterval()
+ {
+ if ($this->burst === null) {
+ return $this->interval;
+ }
+
+ return $this->attempts >= $this->burst
+ ? $this->finalInterval
+ : $this->interval;
+ }
+
+ protected function assertNotRunning()
+ {
+ if ($this->deferred) {
+ throw new RuntimeException(
+ 'Cannot re-run RetryUnless while already running'
+ );
+ }
+ }
+
+ protected function removeEventualTimer()
+ {
+ if ($this->timer) {
+ $this->loop->cancelTimer($this->timer);
+ $this->timer = null;
+ }
+ }
+
+ protected function rejectEventualDeferred($reason)
+ {
+ if ($this->deferred !== null) {
+ $deferred = $this->deferred;
+ $this->deferred = null;
+ $deferred->reject($reason);
+ }
+ }
+
+ public function cancel($reason = null)
+ {
+ $this->removeEventualTimer();
+ $this->rejectEventualDeferred($reason ?: 'cancelled');
+ }
+
+ public function __destruct()
+ {
+ $this->removeEventualTimer();
+ $this->rejectEventualDeferred('RetryUnless has been destructed');
+
+ $this->loop = null;
+ }
+}
diff --git a/vendor/gipfl/simple-daemon/composer.json b/vendor/gipfl/simple-daemon/composer.json
new file mode 100644
index 0000000..1815081
--- /dev/null
+++ b/vendor/gipfl/simple-daemon/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "gipfl/simple-daemon",
+ "description": "Run a simple daemon",
+ "type": "library",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "gipfl\\SimpleDaemon\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "require": {
+ "php": ">=5.6.0",
+ "ext-pcntl": "*",
+ "gipfl/systemd": ">=0.3",
+ "react/promise": "^2",
+ "react/event-loop": ">=1.1",
+ "psr/log": ">=1.0",
+ "gipfl/json": ">=0.1",
+ "evenement/evenement": "*",
+ "gipfl/cli": ">=0.5",
+ "react/promise-timer": ">=1.5"
+ }
+}
diff --git a/vendor/gipfl/simple-daemon/src/Daemon.php b/vendor/gipfl/simple-daemon/src/Daemon.php
new file mode 100644
index 0000000..8d08871
--- /dev/null
+++ b/vendor/gipfl/simple-daemon/src/Daemon.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace gipfl\SimpleDaemon;
+
+use Exception;
+use gipfl\Cli\Process;
+use gipfl\SystemD\NotifySystemD;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use function React\Promise\resolve;
+use function React\Promise\Timer\timeout;
+use function sprintf;
+
+class Daemon implements LoggerAwareInterface
+{
+ use LoggerAwareTrait;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /** @var NotifySystemD|boolean */
+ protected $systemd;
+
+ /** @var DaemonTask[] */
+ protected $daemonTasks = [];
+
+ /** @var bool */
+ protected $tasksStarted = false;
+
+ /** @var bool */
+ protected $reloading = false;
+
+ public function run(LoopInterface $loop)
+ {
+ if ($this->logger === null) {
+ $this->setLogger(new NullLogger());
+ }
+ $this->loop = $loop;
+ $this->registerSignalHandlers();
+ $this->systemd = NotifySystemD::ifRequired($loop);
+ $loop->futureTick(function () {
+ if ($this->systemd) {
+ $this->systemd->setReady();
+ }
+
+ $this->startTasks();
+ });
+ }
+
+ public function attachTask(DaemonTask $task)
+ {
+ if ($task instanceof LoggerAwareInterface) {
+ $task->setLogger($this->logger ?: new NullLogger());
+ }
+
+ $this->daemonTasks[] = $task;
+ if ($this->tasksStarted) {
+ $this->startTask($task);
+ }
+ }
+
+ protected function startTasks()
+ {
+ $this->tasksStarted = true;
+ foreach ($this->daemonTasks as $task) {
+ $this->startTask($task);
+ }
+ }
+
+ protected function startTask(DaemonTask $task)
+ {
+ if ($this->systemd && $task instanceof SystemdAwareTask) {
+ $task->setSystemd($this->systemd);
+ }
+
+ $task->start($this->loop);
+ }
+
+ protected function stopTasks()
+ {
+ if (empty($this->daemonTasks)) {
+ return resolve();
+ }
+
+ $deferred = new Deferred();
+ foreach ($this->daemonTasks as $id => $task) {
+ $task->stop()->always(function () use ($id, $deferred) {
+ unset($this->daemonTasks[$id]);
+ if (empty($this->daemonTasks)) {
+ $this->tasksStarted = false;
+ $this->loop->futureTick(function () use ($deferred) {
+ $deferred->resolve();
+ });
+ }
+ });
+ }
+
+ return $deferred->promise();
+ }
+
+ protected function registerSignalHandlers()
+ {
+ $func = function ($signal) use (&$func) {
+ $this->shutdownWithSignal($signal, $func);
+ };
+ $funcReload = function () {
+ $this->reload();
+ };
+ $this->loop->addSignal(SIGHUP, $funcReload);
+ $this->loop->addSignal(SIGINT, $func);
+ $this->loop->addSignal(SIGTERM, $func);
+ }
+
+ protected function shutdownWithSignal($signal, &$func)
+ {
+ $this->loop->removeSignal($signal, $func);
+ $this->shutdown();
+ }
+
+ public function reload()
+ {
+ if ($this->reloading) {
+ $this->logger->error('Ignoring reload request, reload is already in progress');
+ return;
+ }
+ $this->reloading = true;
+ $this->logger->notice('Stopping tasks, going gown for reload now');
+ if ($this->systemd) {
+ $this->systemd->setReloading('Reloading the main process');
+ }
+ $this->stopTasks()->then(function () {
+ $this->logger->notice('Everything stopped, restarting');
+ Process::restart();
+ });
+ }
+
+ public function shutdown()
+ {
+ timeout($this->stopTasks(), 5, $this->loop)->then(function () {
+ $this->loop->stop();
+ }, function (Exception $e) {
+ if ($this->logger) {
+ $this->logger->error(sprintf(
+ 'Failed to safely shutdown, stopping anyways: %s',
+ $e->getMessage()
+ ));
+ }
+ $this->loop->addTimer(0.1, function () {
+ $this->loop->stop();
+ });
+ });
+ }
+}
diff --git a/vendor/gipfl/simple-daemon/src/DaemonState.php b/vendor/gipfl/simple-daemon/src/DaemonState.php
new file mode 100644
index 0000000..923a546
--- /dev/null
+++ b/vendor/gipfl/simple-daemon/src/DaemonState.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace gipfl\SimpleDaemon;
+
+use Evenement\EventEmitterInterface;
+use Evenement\EventEmitterTrait;
+use gipfl\Json\JsonSerialization;
+use function implode;
+use function strlen;
+
+class DaemonState implements JsonSerialization, EventEmitterInterface
+{
+ use EventEmitterTrait;
+
+ const ON_CHANGE = 'change';
+
+ /** @var string */
+ protected $processTitle;
+
+ /** @var ?string */
+ protected $state;
+
+ /** @var ?string */
+ protected $currentProcessTitle;
+
+ /** @var ?string */
+ protected $currentMessage;
+
+ /** @var string[] */
+ protected $componentStates = [];
+
+ /**
+ * @return string
+ */
+ public function getProcessTitle()
+ {
+ return $this->processTitle;
+ }
+
+ /**
+ * @param string $processTitle
+ * @return DaemonState
+ */
+ public function setProcessTitle($processTitle)
+ {
+ $this->processTitle = $processTitle;
+ $this->refreshMessage();
+ return $this;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ /**
+ * @param string|null $state
+ * @return DaemonState
+ */
+ public function setState($state)
+ {
+ $this->state = $state;
+ $this->refreshMessage();
+
+ return $this;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getCurrentMessage()
+ {
+ return $this->currentMessage;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getComponentStates()
+ {
+ return $this->componentStates;
+ }
+
+ /**
+ * @param string[] $componentStates
+ * @return DaemonState
+ */
+ public function setComponentStates($componentStates)
+ {
+ $this->componentStates = $componentStates;
+ $this->refreshMessage();
+ return $this;
+ }
+
+ /**
+ * @param string $name
+ * @param string $stateMessage
+ * @return $this
+ */
+ public function setComponentState($name, $stateMessage)
+ {
+ if ($stateMessage === null) {
+ unset($this->componentStates[$name]);
+ } else {
+ $this->componentStates[$name] = $stateMessage;
+ }
+ $this->refreshMessage();
+
+ return $this;
+ }
+
+ public function getComponentState($name)
+ {
+ if (isset($this->componentStates[$name])) {
+ return $this->componentStates[$name];
+ }
+
+ return null;
+ }
+
+ public static function fromSerialization($any)
+ {
+ $self = new static();
+ if (isset($any->state)) {
+ $self->state = $any->state;
+ }
+ if (isset($any->currentMessage)) {
+ $self->currentMessage = $any->currentMessage;
+ }
+ if (isset($any->processTitle)) {
+ $self->processTitle = $any->processTitle;
+ }
+ if (isset($any->components)) {
+ $self->componentStates = $any->components;
+ }
+
+ return $self;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return (object) [
+ 'state' => $this->state,
+ 'currentMessage' => $this->currentMessage,
+ 'processTitle' => $this->processTitle,
+ 'components' => $this->componentStates
+ ];
+ }
+
+ protected function refreshMessage()
+ {
+ $messageParts = [];
+ $state = $this->getState();
+ if ($state !== null && strlen($state)) {
+ $messageParts[] = $state;
+ }
+ foreach ($this->getComponentStates() as $component => $message) {
+ $messageParts[] = "$component: $message";
+ }
+
+ $message = implode(', ', $messageParts);
+ if ($message !== $this->currentMessage || $this->processTitle !== $this->currentProcessTitle) {
+ $this->currentMessage = $message;
+ $this->currentProcessTitle = $this->processTitle;
+ $this->emit(self::ON_CHANGE, [$this->currentProcessTitle, $this->currentMessage]);
+ }
+ }
+}
diff --git a/vendor/gipfl/simple-daemon/src/DaemonTask.php b/vendor/gipfl/simple-daemon/src/DaemonTask.php
new file mode 100644
index 0000000..70fc71a
--- /dev/null
+++ b/vendor/gipfl/simple-daemon/src/DaemonTask.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace gipfl\SimpleDaemon;
+
+use React\EventLoop\LoopInterface;
+use React\Promise\ExtendedPromiseInterface;
+
+interface DaemonTask
+{
+ /**
+ * @param LoopInterface $loop
+ * @return ExtendedPromiseInterface
+ */
+ public function start(LoopInterface $loop);
+
+ /**
+ * @return ExtendedPromiseInterface
+ */
+ public function stop();
+}
diff --git a/vendor/gipfl/simple-daemon/src/SystemdAwareTask.php b/vendor/gipfl/simple-daemon/src/SystemdAwareTask.php
new file mode 100644
index 0000000..3901d46
--- /dev/null
+++ b/vendor/gipfl/simple-daemon/src/SystemdAwareTask.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace gipfl\SimpleDaemon;
+
+use gipfl\SystemD\NotifySystemD;
+
+interface SystemdAwareTask
+{
+ /**
+ * @param NotifySystemD $systemd
+ */
+ public function setSystemd(NotifySystemD $systemd);
+}
diff --git a/vendor/gipfl/socket/composer.json b/vendor/gipfl/socket/composer.json
new file mode 100644
index 0000000..cc19780
--- /dev/null
+++ b/vendor/gipfl/socket/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "gipfl/socket",
+ "description": "Helpful ReactPHP socket utility classes",
+ "type": "library",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Socket\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ext-posix": "*",
+ "ext-sockets": "*",
+ "evenement/evenement": ">=2.0",
+ "gipfl/json": ">=0.1",
+ "react/event-loop": ">=1.0",
+ "react/socket": ">=1.0"
+ }
+}
diff --git a/vendor/gipfl/socket/src/ConnectionList.php b/vendor/gipfl/socket/src/ConnectionList.php
new file mode 100644
index 0000000..47ea489
--- /dev/null
+++ b/vendor/gipfl/socket/src/ConnectionList.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace gipfl\Socket;
+
+use Evenement\EventEmitterInterface;
+use Evenement\EventEmitterTrait;
+use InvalidArgumentException;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Socket\ConnectionInterface;
+use SplObjectStorage;
+use function React\Promise\all;
+
+/**
+ * @method ConnectionInterface current
+ */
+class ConnectionList extends SplObjectStorage implements EventEmitterInterface
+{
+ use EventEmitterTrait;
+
+ const ON_ATTACHED = 'attached';
+ const ON_DETACHED = 'detached';
+
+ /** @var LoopInterface */
+ protected $loop;
+
+ public function __construct(LoopInterface $loop)
+ {
+ $this->loop = $loop;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function attach($object, $info = null)
+ {
+ if (! $object instanceof ConnectionInterface || $info !== null) {
+ throw new InvalidArgumentException(sprintf(
+ 'Can attach only %s instances',
+ ConnectionInterface::class
+ ));
+ }
+ $object->on('close', function () use ($object) {
+ $this->detach($object);
+ });
+
+ parent::attach($object, $info);
+ $this->emit(self::ON_ATTACHED, [$object]);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function detach($object)
+ {
+ parent::detach($object);
+ $this->emit(self::ON_DETACHED, [$object]);
+ }
+
+ public function close()
+ {
+ $pending = [];
+ foreach ($this as $connection) {
+ $pending[] = $this->closeConnection($connection, $this->loop);
+ }
+
+ return all($pending);
+ }
+
+ public static function closeConnection(ConnectionInterface $connection, LoopInterface $loop, $timeout = 5)
+ {
+ $deferred = new Deferred();
+ $connection->end();
+ if ($connection->isWritable() || $connection->isReadable()) {
+ $timer = $loop->addTimer($timeout, function () use ($connection, $deferred) {
+ $connection->close();
+ });
+
+ $connection->on('close', function () use ($deferred, $timer) {
+ $this->loop->cancelTimer($timer);
+ $deferred->resolve();
+ });
+ } else {
+ $loop->futureTick(function () use ($deferred) {
+ $deferred->resolve();
+ });
+ }
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/gipfl/socket/src/UnixSocketInspection.php b/vendor/gipfl/socket/src/UnixSocketInspection.php
new file mode 100644
index 0000000..e1e9819
--- /dev/null
+++ b/vendor/gipfl/socket/src/UnixSocketInspection.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace gipfl\Socket;
+
+use RuntimeException;
+use React\Socket\ConnectionInterface;
+use function array_shift;
+use function file_exists;
+use function is_int;
+use function posix_getgrgid;
+use function posix_getpwuid;
+use function preg_split;
+use function socket_get_option;
+use function socket_import_stream;
+use function stat;
+use function strlen;
+use function trim;
+
+class UnixSocketInspection
+{
+ /**
+ * @param ConnectionInterface $connection
+ * @return UnixSocketPeer
+ */
+ public static function getPeer(ConnectionInterface $connection)
+ {
+ $socket = socket_import_stream($connection->stream);
+ $remotePid = static::getRemotePidFromSocket($socket);
+ $stat = static::statProcFile($remotePid);
+ $uid = $stat['uid'];
+ $gid = $stat['gid'];
+ $userInfo = static::getUserInfo($uid);
+ $gecosParts = preg_split('/,/', $userInfo['gecos']);
+ $fullName = trim(array_shift($gecosParts));
+ $groupInfo = static::getGroupInfo($gid);
+
+ return new UnixSocketPeer(
+ $remotePid,
+ $uid,
+ $gid,
+ $userInfo['name'],
+ strlen($fullName) ? $fullName : null,
+ $groupInfo['name']
+ );
+ }
+
+ protected static function getRemotePidFromSocket($socket)
+ {
+ // SO_PEERCRED = 17
+ $remotePid = socket_get_option($socket, SOL_SOCKET, 17);
+ if (! is_int($remotePid) || ! $remotePid > 0) {
+ throw new RuntimeException("Remote PID expected, got " . var_export($remotePid));
+ }
+
+ return $remotePid;
+ }
+
+ protected static function statProcFile($pid)
+ {
+ $procDir = "/proc/$pid";
+ if (file_exists($procDir)) {
+ return stat($procDir);
+ } else {
+ throw new RuntimeException("Got no proc dir ($procDir) for remote node");
+ }
+ }
+
+ protected static function getUserInfo($uid)
+ {
+ $userInfo = posix_getpwuid($uid);
+
+ if ($userInfo === false) {
+ throw new RuntimeException("Unable to resolve remote UID '$uid'");
+ }
+
+ return $userInfo;
+ }
+
+ protected static function getGroupInfo($gid)
+ {
+ $groupInfo = posix_getgrgid($gid);
+
+ if ($groupInfo === false) {
+ throw new RuntimeException("Unable to resolve remote GID '$gid'");
+ }
+
+ return $groupInfo;
+ }
+}
diff --git a/vendor/gipfl/socket/src/UnixSocketPeer.php b/vendor/gipfl/socket/src/UnixSocketPeer.php
new file mode 100644
index 0000000..409487b
--- /dev/null
+++ b/vendor/gipfl/socket/src/UnixSocketPeer.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace gipfl\Socket;
+
+use gipfl\Json\JsonSerialization;
+
+class UnixSocketPeer implements JsonSerialization
+{
+ /** @var int */
+ protected $pid;
+
+ /** @var int */
+ protected $uid;
+
+ /** @var int */
+ protected $gid;
+
+ /** @var string */
+ protected $username;
+
+ /** @var ?string */
+ protected $fullName;
+
+ /** @var string */
+ protected $groupName;
+
+ public function __construct($pid, $uid, $gid, $username, $fullName, $groupName)
+ {
+ $this->pid = $pid;
+ $this->uid = $uid;
+ $this->gid = $gid;
+ $this->username = $username;
+ $this->fullName = $fullName;
+ $this->groupName = $groupName;
+ }
+
+ /**
+ * @return int
+ */
+ public function getPid()
+ {
+ return $this->pid;
+ }
+
+ /**
+ * @return int
+ */
+ public function getUid()
+ {
+ return $this->uid;
+ }
+
+ /**
+ * @return int
+ */
+ public function getGid()
+ {
+ return $this->gid;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getFullName()
+ {
+ return $this->fullName;
+ }
+
+ /**
+ * @return string
+ */
+ public function getGroupName()
+ {
+ return $this->groupName;
+ }
+
+ public static function fromSerialization($any)
+ {
+ return new static($any->pid, $any->uid, $any->gid, $any->username, $any->fullName, $any->groupName);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return (object) [
+ 'pid' => $this->pid,
+ 'uid' => $this->uid,
+ 'gid' => $this->gid,
+ 'username' => $this->username,
+ 'fullName' => $this->fullName,
+ 'groupName' => $this->groupName,
+ ];
+ }
+}
diff --git a/vendor/gipfl/stream/composer.json b/vendor/gipfl/stream/composer.json
new file mode 100644
index 0000000..3bbcb6a
--- /dev/null
+++ b/vendor/gipfl/stream/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "gipfl/stream",
+ "description": "Helpful ReactPHP stream utility classes",
+ "type": "library",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Stream\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "require": {
+ "react/event-loop": ">=1.0",
+ "react/stream": ">=1.0"
+ },
+ "require-dev": {
+ "gipfl/test": ">=0.1.1",
+ "phpunit/phpunit": "^9.3 || ^7.5 || ^6.5 || ^5.7",
+ "squizlabs/php_codesniffer": "^3.6"
+ }
+}
diff --git a/vendor/gipfl/stream/src/BufferedLineReader.php b/vendor/gipfl/stream/src/BufferedLineReader.php
new file mode 100644
index 0000000..bd43155
--- /dev/null
+++ b/vendor/gipfl/stream/src/BufferedLineReader.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace gipfl\Stream;
+
+use Evenement\EventEmitterTrait;
+use React\EventLoop\LoopInterface;
+use React\Stream\WritableStreamInterface;
+use function strlen;
+use function strpos;
+use function substr;
+
+class BufferedLineReader implements WritableStreamInterface
+{
+ use EventEmitterTrait;
+
+ /** @var LoopInterface */
+ protected $loop;
+
+ protected $buffer = '';
+
+ protected $writable = true;
+
+ /** @var string */
+ protected $separator;
+
+ /** @var int */
+ protected $separatorLength;
+
+ protected $process;
+
+ // protected $maxBufferSize; // Not yet. Do we need this?
+
+ /**
+ * @param string $separator
+ * @param LoopInterface $loop
+ */
+ public function __construct($separator, LoopInterface $loop)
+ {
+ $this->loop = $loop;
+ $this->separator = $separator;
+ $this->separatorLength = strlen($separator);
+ $this->process = function () {
+ $this->processBuffer();
+ };
+ }
+
+ protected function processBuffer()
+ {
+ $lastPos = 0;
+ while (false !== ($pos = strpos($this->buffer, $this->separator, $lastPos))) {
+ $this->emit('line', [substr($this->buffer, $lastPos, $pos - $lastPos)]);
+ $lastPos = $pos + $this->separatorLength;
+ }
+ if ($lastPos !== 0) {
+ $this->buffer = substr($this->buffer, $lastPos);
+ }
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (! $this->writable) {
+ return false;
+ }
+ $this->buffer .= $data;
+ if (strpos($data, $this->separator) !== false) {
+ $this->loop->futureTick($this->process);
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if ($data !== null) {
+ $this->buffer .= $data;
+ }
+ $this->close();
+ }
+
+ public function close()
+ {
+ $this->writable = false;
+ $this->processBuffer();
+ $remainingBuffer = $this->buffer;
+ $this->buffer = '';
+ if ($length = strlen($remainingBuffer)) {
+ $this->emit('error', [new \Exception(sprintf(
+ 'There are %d unprocessed bytes in our buffer: %s',
+ $length,
+ substr($remainingBuffer, 0, 64)
+ ))]);
+ }
+ $this->emit('close');
+ }
+}
diff --git a/vendor/gipfl/systemd/LICENSE b/vendor/gipfl/systemd/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/systemd/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/systemd/composer.json b/vendor/gipfl/systemd/composer.json
new file mode 100644
index 0000000..d751bee
--- /dev/null
+++ b/vendor/gipfl/systemd/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "gipfl/systemd",
+ "description": "SystemD-related library",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\SystemD\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.3",
+ "ext-posix": "*",
+ "ext-sockets": "*",
+ "react/event-loop": "^1.0"
+ }
+}
diff --git a/vendor/gipfl/systemd/src/NotificationSocket.php b/vendor/gipfl/systemd/src/NotificationSocket.php
new file mode 100644
index 0000000..fe4a687
--- /dev/null
+++ b/vendor/gipfl/systemd/src/NotificationSocket.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace gipfl\SystemD;
+
+use RuntimeException;
+use function file_exists;
+use function is_writable;
+
+class NotificationSocket
+{
+ /** @var resource */
+ protected $socket;
+
+ public function __construct($path)
+ {
+ if (@file_exists($path) && is_writable($path)) {
+ $this->path = $path;
+ } else {
+ throw new RuntimeException("Unix Socket '$path' is not writable");
+ }
+
+ $this->connect();
+ }
+
+ /**
+ * Send custom parameters to systemd
+ *
+ * This is for internal use only, but might be used to test new functionality
+ *
+ * @param array $params
+ * @internal
+ */
+ public function send(array $params)
+ {
+ $message = $this->buildMessage($params);
+ $length = \strlen($message);
+ $result = @socket_send($this->socket, $message, $length, 0);
+ if ($result === false) {
+ $error = \socket_last_error($this->socket);
+
+ throw new RuntimeException(
+ "Failed to send to SystemD: " . \socket_strerror($error),
+ $error
+ );
+ }
+ if ($result !== $length) {
+ throw new RuntimeException(
+ "Wanted to send $length Bytes to SystemD, only $result have been sent"
+ );
+ }
+ }
+
+ /**
+ * Get the path to the the systemd notification socket
+ *
+ * Usually /run/systemd/notify or similar
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Transforms a key/value array into a string like "key1=val1\nkey2=val2"
+ *
+ * @param array $params
+ * @return string
+ */
+ protected function buildMessage(array $params)
+ {
+ $message = '';
+ foreach ($params as $key => $value) {
+ $message .= "$key=$value\n";
+ }
+
+ return $message;
+ }
+
+ /**
+ * Connect to the discovered socket
+ *
+ * Will be /run/systemd/notify or similar. No async logic, as this
+ * shouldn't block. If systemd blocks we're dead anyways, so who cares
+ */
+ protected function connect()
+ {
+ $path = $this->path;
+ $socket = @\socket_create(AF_UNIX, SOCK_DGRAM, 0);
+ if ($socket === false) {
+ throw new RuntimeException('Unable to create socket');
+ }
+
+ if (! @\socket_connect($socket, $path)) {
+ $error = \socket_last_error($socket);
+
+ throw new RuntimeException(
+ "Unable to connect to unix domain socket $path: " . \socket_strerror($error),
+ $error
+ );
+ }
+
+ $this->socket = $socket;
+ }
+
+ /**
+ * Disconnect the socket if connected
+ */
+ public function disconnect()
+ {
+ if (\is_resource($this->socket)) {
+ @\socket_close($this->socket);
+ $this->socket = null;
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+}
diff --git a/vendor/gipfl/systemd/src/NotifySystemD.php b/vendor/gipfl/systemd/src/NotifySystemD.php
new file mode 100644
index 0000000..0561fd6
--- /dev/null
+++ b/vendor/gipfl/systemd/src/NotifySystemD.php
@@ -0,0 +1,292 @@
+<?php
+
+namespace gipfl\SystemD;
+
+use Exception;
+use InvalidArgumentException;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+use RuntimeException;
+
+class NotifySystemD
+{
+ /** @var LoopInterface */
+ protected $loop;
+
+ /** @var TimerInterface */
+ protected $timer;
+
+ /** @var string|null */
+ protected $path;
+
+ /** @var string|null */
+ protected $status;
+
+ /** @var float */
+ protected $interval;
+
+ /** @var bool */
+ protected $reloading = false;
+
+ /** @var bool */
+ protected $ready = false;
+
+ /** @var bool */
+ protected $failed = false;
+
+ /** @var string|null The Invocation ID (128bit UUID) if available */
+ protected $invocationId;
+
+ protected $notificationSocket;
+
+ /**
+ * NotifySystemD constructor.
+ * @param string $notifySocket
+ * @param int $intervalSecs
+ */
+ public function __construct($notifySocket, $intervalSecs = 1)
+ {
+ $this->interval = $intervalSecs;
+ $this->notificationSocket = new NotificationSocket($notifySocket);
+ }
+
+ /**
+ * Starts sending WatchDog pings
+ *
+ * @param LoopInterface $loop
+ */
+ public function run(LoopInterface $loop)
+ {
+ $this->loop = $loop;
+ $ping = function () {
+ try {
+ $this->pingWatchDog();
+ } catch (Exception $e) {
+ printf(
+ "<%d>Failed to ping systemd watchdog: %s\n",
+ LOG_ERR,
+ $e->getMessage()
+ );
+ }
+ };
+ $loop->futureTick($ping);
+ $this->timer = $loop->addPeriodicTimer($this->interval, $ping);
+
+ return $this;
+ }
+
+ /**
+ * Stop sending watchdog notifications
+ *
+ * Usually there is no need to do so, and the destructor does this anyways
+ */
+ public function stop()
+ {
+ if ($this->timer !== null) {
+ $this->loop->cancelTimer($this->timer);
+ }
+ }
+
+ public static function ifRequired(LoopInterface $loop, $env = null)
+ {
+ if ($env === null) {
+ $env = $_SERVER;
+ }
+
+ if (! systemd::startedThisProcess($env)) {
+ return false;
+ }
+
+ if (isset($env['WATCHDOG_USEC'])) {
+ $interval = $env['WATCHDOG_USEC'] / 2000000; // Half of that time
+ } else {
+ $interval = 1;
+ }
+
+ return (new static($env['NOTIFY_SOCKET'], $interval))
+ ->eventuallySetInvocationIdFromEnv($env)
+ ->run($loop);
+ }
+
+ /**
+ * Extend the Watchdog timeout
+ *
+ * Useful to inform systemd before slow startup/shutdown operations. This
+ * is available since systemd v236. Older versions silently ignore this.
+ *
+ * @param float $seconds
+ */
+ public function extendTimeout($seconds)
+ {
+ $this->send(['EXTEND_TIMEOUT_USEC' => (int) $seconds * 1000000]);
+ }
+
+ /**
+ * Send a notification to the systemd watchdog
+ */
+ public function pingWatchDog()
+ {
+ $this->send(['WATCHDOG' => '1']);
+ }
+
+ /**
+ * Set the (visible) service status
+ *
+ * @param $status
+ */
+ public function setStatus($status)
+ {
+ if ($status !== $this->status) {
+ $this->status = $status;
+ $this->send(['STATUS' => $status]);
+ }
+ }
+
+ public function setReloading($status = null)
+ {
+ $this->ready = false;
+ $this->status = $status;
+ $params = ['RELOADING' => '1'];
+ if ($status !== null) {
+ $params['STATUS'] = $status;
+ }
+ $this->send($params);
+ }
+
+ public function setError($error, $status = null)
+ {
+ $this->ready = false;
+ $this->status = $status;
+ if ($error instanceof Exception) {
+ $errNo = $error->getCode();
+ $status = $status ?: $error->getMessage();
+ } elseif (\is_int($error)) {
+ $errNo = $error;
+ } else {
+ throw new InvalidArgumentException(
+ 'Error has to be an Exception or an Integer'
+ );
+ }
+ $params = [];
+ if ($status !== null) {
+ $params['STATUS'] = $status;
+ $this->status = $status;
+ }
+
+ $params = ['ERRNO' => (string) $errNo];
+ $this->send($params);
+ $this->failed = true;
+ }
+
+ public function setReady($status = null)
+ {
+ $this->ready = true;
+ $params = [
+ 'READY' => '1',
+ 'MAINPID' => \posix_getpid(),
+ ];
+
+ if ($status !== null) {
+ $params['STATUS'] = $status;
+ $this->status = $status;
+ }
+
+ $this->send($params);
+ }
+
+ /**
+ * Returns a 128bit uuid (16 hex characters) Invocation ID if available,
+ * null in case there isn't
+ *
+ * @return string|null
+ */
+ public function getInvocationId()
+ {
+ return $this->invocationId;
+ }
+
+ /**
+ * Whether we got an Invocation ID
+ *
+ * @return bool
+ */
+ public function hasInvocationId()
+ {
+ return $this->invocationId !== null;
+ }
+
+ /**
+ * Get the path to the the systemd notification socket
+ *
+ * Usually /run/systemd/notify or similar
+ *
+ * @return string
+ */
+ public function getSocketPath()
+ {
+ return $this->notificationSocket->getPath();
+ }
+
+ /**
+ * Our Watchdog interval in seconds
+ *
+ * This is a float value: half of WATCHDOG_USEC if given - otherwise 1 second
+ *
+ * @return float
+ */
+ public function getWatchdogInterval()
+ {
+ return $this->interval;
+ }
+
+ /**
+ * Send custom parameters to systemd
+ *
+ * This is for internal use only, but might be used to test new functionality
+ *
+ * @param array $params
+ * @internal
+ */
+ public function send(array $params)
+ {
+ if ($this->failed) {
+ throw new RuntimeException('Cannot notify SystemD after failing');
+ }
+
+ $this->notificationSocket->send($params);
+ }
+
+ /**
+ * If INVOCATION_ID is available in the given ENV array: keep it
+ *
+ * Fails in case we do not get an 128bit string
+ *
+ * @param array $env
+ * @return $this
+ */
+ protected function eventuallySetInvocationIdFromEnv(array $env)
+ {
+ $key = 'INVOCATION_ID';
+ if (isset($env[$key])) {
+ if (\strlen($env[$key]) === 32) {
+ $this->invocationId = $env[$key];
+ } else {
+ throw new RuntimeException(sprintf(
+ 'Unsupported %s="%s"',
+ $key,
+ $env['$key']
+ ));
+ }
+ }
+
+ return $this;
+ }
+
+ public function __destruct()
+ {
+ $this->notificationSocket->disconnect();
+ $this->notificationSocket = null;
+ $this->stop();
+ unset($this->loop);
+ }
+}
diff --git a/vendor/gipfl/systemd/src/systemd.php b/vendor/gipfl/systemd/src/systemd.php
new file mode 100644
index 0000000..7894a8f
--- /dev/null
+++ b/vendor/gipfl/systemd/src/systemd.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace gipfl\SystemD;
+
+class systemd
+{
+ /**
+ * @param null $env
+ * @return bool
+ */
+ public static function startedThisProcess($env = null)
+ {
+ if ($env === null) {
+ $env = $_SERVER;
+ }
+
+ return isset($env['NOTIFY_SOCKET']);
+ }
+}
diff --git a/vendor/gipfl/translation/LICENSE b/vendor/gipfl/translation/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/translation/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/translation/composer.json b/vendor/gipfl/translation/composer.json
new file mode 100644
index 0000000..35608bb
--- /dev/null
+++ b/vendor/gipfl/translation/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "gipfl/translation",
+ "description": "Translation helpers",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Translation\\": "src"
+ }
+ }
+}
diff --git a/vendor/gipfl/translation/src/NoTranslator.php b/vendor/gipfl/translation/src/NoTranslator.php
new file mode 100644
index 0000000..3ea64ef
--- /dev/null
+++ b/vendor/gipfl/translation/src/NoTranslator.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace gipfl\Translation;
+
+class NoTranslator implements TranslatorInterface
+{
+ public function translate($string)
+ {
+ return $string;
+ }
+}
diff --git a/vendor/gipfl/translation/src/StaticTranslator.php b/vendor/gipfl/translation/src/StaticTranslator.php
new file mode 100644
index 0000000..d06572b
--- /dev/null
+++ b/vendor/gipfl/translation/src/StaticTranslator.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace gipfl\Translation;
+
+class StaticTranslator
+{
+ /** @var TranslatorInterface */
+ private static $translator;
+
+ public static function get()
+ {
+ if (self::$translator === null) {
+ static::setNoTranslator();
+ }
+
+ return static::$translator;
+ }
+
+ public static function setNoTranslator()
+ {
+ static::set(new NoTranslator());
+ }
+
+ /**
+ * @param TranslatorInterface $translator
+ */
+ public static function set(TranslatorInterface $translator)
+ {
+ self::$translator = $translator;
+ }
+}
diff --git a/vendor/gipfl/translation/src/TranslationHelper.php b/vendor/gipfl/translation/src/TranslationHelper.php
new file mode 100644
index 0000000..02cfdfc
--- /dev/null
+++ b/vendor/gipfl/translation/src/TranslationHelper.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace gipfl\Translation;
+
+trait TranslationHelper
+{
+ /** @var TranslatorInterface */
+ private static $translator;
+
+ /**
+ * @param $string
+ * @param string|null $context
+ * @return string
+ */
+ public function translate($string, $context = null)
+ {
+ return self::getTranslator()->translate($string);
+ }
+
+ public static function getTranslator()
+ {
+ return StaticTranslator::get();
+ }
+
+ public static function setNoTranslator()
+ {
+ StaticTranslator::set(new NoTranslator());
+ }
+
+ /**
+ * @param TranslatorInterface $translator
+ */
+ public static function setTranslator(TranslatorInterface $translator)
+ {
+ StaticTranslator::set($translator);
+ }
+}
diff --git a/vendor/gipfl/translation/src/TranslatorInterface.php b/vendor/gipfl/translation/src/TranslatorInterface.php
new file mode 100644
index 0000000..8026953
--- /dev/null
+++ b/vendor/gipfl/translation/src/TranslatorInterface.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace gipfl\Translation;
+
+interface TranslatorInterface
+{
+ public function translate($string);
+}
diff --git a/vendor/gipfl/translation/src/WrapTranslator.php b/vendor/gipfl/translation/src/WrapTranslator.php
new file mode 100644
index 0000000..d17fcbd
--- /dev/null
+++ b/vendor/gipfl/translation/src/WrapTranslator.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace gipfl\Translation;
+
+class WrapTranslator implements TranslatorInterface
+{
+ /** @var callable */
+ private $callback;
+
+ /** @var TranslatorInterface */
+ private $wrapped;
+
+ public function __construct(TranslatorInterface $wrapped, callable $callback)
+ {
+ $this->wrapped = $wrapped;
+ $this->callback = $callback;
+ }
+
+ public function translate($string)
+ {
+ return call_user_func_array(
+ $this->callback,
+ [$this->wrapped->translate($string)]
+ );
+ }
+}
diff --git a/vendor/gipfl/web/LICENSE b/vendor/gipfl/web/LICENSE
new file mode 100644
index 0000000..dd88e09
--- /dev/null
+++ b/vendor/gipfl/web/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2018 Thomas Gelf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gipfl/web/composer.json b/vendor/gipfl/web/composer.json
new file mode 100644
index 0000000..ce28dd5
--- /dev/null
+++ b/vendor/gipfl/web/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "gipfl/web",
+ "description": "Various web widgets",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Thomas Gelf",
+ "email": "thomas@gelf.net"
+ }
+ ],
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\Web\\": "src"
+ }
+ },
+ "require": {
+ "php": ">=5.6.0",
+ "ipl/html": ">=0.3",
+ "gipfl/translation": ">=0.1.1"
+ }
+}
diff --git a/vendor/gipfl/web/public/css/01-gipfl-base.less b/vendor/gipfl/web/public/css/01-gipfl-base.less
new file mode 100644
index 0000000..c352858
--- /dev/null
+++ b/vendor/gipfl/web/public/css/01-gipfl-base.less
@@ -0,0 +1,8 @@
+@gipfl-color-low-sat-blue: #dae3e6;
+// map Icinga vars, to avoid warnings in other files
+@gipfl-color-main: @icinga-blue;
+@gipfl-color-text: @text-color;
+@gipfl-color-gray-light: @gray-light;
+@gipfl-bg-element: #f2f1f0;
+@gipfl-bg-element: @low-sat-blue;
+@gipfl-icon-font: 'ifont';
diff --git a/vendor/gipfl/web/public/css/21-gipfl-collapsible.less b/vendor/gipfl/web/public/css/21-gipfl-collapsible.less
new file mode 100644
index 0000000..da065c1
--- /dev/null
+++ b/vendor/gipfl/web/public/css/21-gipfl-collapsible.less
@@ -0,0 +1,55 @@
+/**
+ * Lightweight support for small collapsible components
+ *
+ * @since v0.6.0
+ */
+.gipfl-collapsible-control {
+ display: block;
+ border-bottom: 1px solid @gipfl-color-gray-light;
+ &:hover {
+ cursor: pointer;
+ text-decoration: none;
+ }
+ &::after {
+ font-family: @gipfl-icon-font;
+ content: '\f103';
+ float: right;
+ margin-right: 0.5em;
+ color: @gipfl-color-gray-light;
+ }
+ &:hover {
+ &::after {
+ color: @gipfl-color-text;
+ }
+ }
+ &:focus {
+ text-decoration: none;
+ &::after {
+ color: @gipfl-color-main;
+ }
+ }
+}
+
+.gipfl-collapsible .collapsed {
+ :not(.gipfl-collapsible-control) {
+ display: none;
+ }
+ .gipfl-collapsible-control {
+ &::after {
+ content: '\e87a';
+ }
+ }
+}
+
+ul.gipfl-collapsible {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ li {
+ margin: 0;
+ }
+ .gipfl-collapsible-control {
+ font-weight: bold;
+ line-height: 2em;
+ }
+}
diff --git a/vendor/gipfl/web/public/css/21-gipfl-widget-hint.less b/vendor/gipfl/web/public/css/21-gipfl-widget-hint.less
new file mode 100644
index 0000000..2a4f9f9
--- /dev/null
+++ b/vendor/gipfl/web/public/css/21-gipfl-widget-hint.less
@@ -0,0 +1,57 @@
+/**
+ * State Hints
+ *
+ * @since v0.6.0
+ */
+div.gipfl-widget-hint {
+ border: 1px solid @text-color;
+ padding: 0.5em;
+ line-height: 2em;
+ max-width: 60em;
+ border-left-width: 3em;
+ border-radius: 0.25em;
+ margin-bottom: 1em;
+ background-color: @body-bg-color;
+ box-shadow: fade(@text-color, 30%) 1em 0 1.5em 0;
+ &:before {
+ position: relative;
+ margin-left: -1.4em;
+ margin-right: 0.5em;
+ height: 100%;
+ vertical-align: middle;
+ font-family: 'ifont';
+ color: white;
+ font-size: 2em;
+ }
+ &.ok {
+ border-color: @color-ok;
+ box-shadow: fade(@color-ok, 30%) 1em 0 1.5em 0;
+ }
+ &.info {
+ border-color: @color-pending;
+ box-shadow: fade(@color-pending, 30%) 1em 0 1.5em 0;
+ }
+ &.warning {
+ border-color: @color-warning;
+ box-shadow: fade(@color-warning, 30%) 1em 0 1.5em 0;
+ }
+ &.error {
+ border-color: @color-critical;
+ box-shadow: fade(@color-critical, 30%) 1em 0 1.5em 0;
+ }
+ &.critical:before, &.error:before {
+ content: '\e881';
+ }
+ &.warning:before {
+ content: '\e881';
+ }
+ &.info:before {
+ content: '\e87d';
+ }
+ &.ok:before {
+ content: '\e803';
+ }
+ a {
+ text-decoration: underline;
+ }
+}
diff --git a/vendor/gipfl/web/public/css/31-gipfl-name-value-table.less b/vendor/gipfl/web/public/css/31-gipfl-name-value-table.less
new file mode 100644
index 0000000..30ae217
--- /dev/null
+++ b/vendor/gipfl/web/public/css/31-gipfl-name-value-table.less
@@ -0,0 +1,27 @@
+table.gipfl-name-value-table {
+ width: 100%;
+ > tbody > tr > th {
+ color: @text-color-light;
+ // Reset default font-weight
+ font-weight: bold;
+ padding-left: 0;
+ text-align: right;
+ vertical-align: top;
+ width: 12em;
+ }
+ > tbody > tr > td {
+ vertical-align: top;
+ }
+}
+
+table.gipfl-name-value-table a {
+ color: @icinga-blue;
+}
+
+table.gipfl-name-value-table pre {
+ background-color: transparent;
+ margin: 0;
+ padding: 0;
+ font-size: unset;
+ display: inline;
+}
diff --git a/vendor/gipfl/web/public/css/40-gipfl-form.less b/vendor/gipfl/web/public/css/40-gipfl-form.less
new file mode 100644
index 0000000..cf6722e
--- /dev/null
+++ b/vendor/gipfl/web/public/css/40-gipfl-form.less
@@ -0,0 +1,69 @@
+form.gipfl-form {
+ input[type="submit"] {
+ margin-right: 0.5em;
+ }
+ input[type="submit"]:first-of-type {
+ background-color: @icinga-blue;
+ color: @text-color-inverted;
+ border: 2px solid @icinga-blue;
+ font-weight: bold;
+ }
+
+ select:not([multiple]) {
+ padding-right: 1.5625em;
+ background-image: url(../img/select-icon.svg);
+ background-repeat: no-repeat;
+ background-position: right center;
+ background-size: contain;
+ }
+
+ input.validated {
+ background-color: fade(@color-ok, 30%);
+ border-color: @color-ok;
+ }
+
+ :not(output):-moz-ui-invalid, :not(output).invalid {
+ background-color: fade(@color-critical, 30%);
+ border-color: @color-critical;
+ box-shadow: none;
+ }
+
+ input[type=text].input-with-button {
+ max-width: 30em;
+ min-width: 18em;
+ width: 80%;
+ margin: 0;
+ border-top-right-radius: unset;
+ border-bottom-right-radius: unset;
+ border-right-style: none;
+ &:focus {
+ border-right-style: none;
+ }
+ }
+
+ input[type=submit].input-element-related-button {
+ width: 20%;
+ max-width: 6em;
+ background-color: @icinga-blue;
+ color: @text-color-inverted;
+ border: 1px solid @icinga-blue;
+ border-radius: 0 3px 3px 0;
+ -moz-border-radius: 0 3px 3px 0;
+ -webkit-border-radius: 0 3px 3px 0;
+ line-height: 2em;
+ height: 2.4em;
+ padding: 0;
+ margin: 0 1em 0 0;
+ &:hover {
+ background-color: @text-color-inverted;
+ color: @icinga-blue;
+ border-color: @icinga-blue;
+ }
+ }
+ p.gipfl-widget-hint {
+ max-width: 52.5em;
+ }
+ p.gipfl-element-description {
+ max-width: 36em;
+ }
+}
diff --git a/vendor/gipfl/web/public/css/41-director-form.less b/vendor/gipfl/web/public/css/41-director-form.less
new file mode 100644
index 0000000..b907593
--- /dev/null
+++ b/vendor/gipfl/web/public/css/41-director-form.less
@@ -0,0 +1,339 @@
+form.gipfl-form {
+ label {
+ line-height: 2em;
+ }
+ dl {
+ margin: 0;
+ padding: 0;
+ &.active {
+ dt label {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ dt {
+ padding: 0;
+ margin: 0.5em 0 0 0;
+ display: inline-block;
+ vertical-align: top;
+ min-width: 8em;
+ max-width: 16em;
+ min-height: 2.5em;
+ width: 27%;
+ label {
+ color: @text-color;
+ font-weight: bold;
+ width: 12em;
+ font-size: inherit;
+
+ &:hover {
+ text-decoration: underline;
+ cursor: pointer;
+ }
+ }
+
+ &.errors label {
+ color: @color-critical;
+ }
+ }
+
+ dd {
+ padding: 0.3em 0.5em;
+ margin: 0;
+ display: inline-block;
+ width: 73%;
+ min-height: 2.5em;
+ vertical-align: top;
+
+ &.errors {
+ input[type=text], select {
+ border-color: @color-critical;
+ }
+ }
+
+ &.full-width {
+ padding: 0.5em;
+ width: 100%;
+ }
+
+ &:after {
+ display: block;
+ content: '';
+ }
+
+ ul.errors, ul.gipfl-form-element-errors {
+ list-style-type: none;
+ padding-left: 0.3em;
+
+ li {
+ color: @colorCritical;
+ padding: 0.3em;
+ }
+ }
+ }
+
+ input[type="text"],
+ input[type="password"],
+ input[type="number"],
+ input[type="datetime-local"],
+ input[type="date"],
+ input[type="time"],
+ textarea,
+ select {
+ background-color: @gipfl-bg-element;
+ }
+
+ .errors label {
+ color: @color-critical;
+ }
+
+ textarea {
+ height: auto;
+ max-width: 100%;
+ }
+
+ input[type=file] {
+ background-color: @text-color-inverted;
+ padding-right: 1em;
+ }
+
+ input[type=submit] {
+ .button();
+ transition: none; // Avoid flickering on autosubmit
+ border-width: 1px;
+ margin-top: 0.5em;
+
+ &:disabled {
+ border-color: @gray-light;
+ // background-color: @gray-light;
+ // color: #fff;
+ cursor: wait;
+ }
+ &:first-of-type {
+ border-width: 2px;
+ }
+ }
+
+ select {
+ border: 1px solid #ddd;
+ cursor: pointer;
+ }
+
+ input,
+ select,
+ select option,
+ textarea {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ }
+
+ select::-ms-expand,
+ input::-ms-expand,
+ textarea::-ms-expand { // for IE 11
+ display: none;
+ }
+
+ input[type=submit].link-button {
+ color: @icinga-blue;
+ background: none;
+ border: none;
+ font-weight: normal;
+ padding: 0;
+ margin: 0;
+ text-align: left;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ ul.form-errors {
+ list-style-type: none;
+ margin-bottom: 0.5em;
+ padding: 0;
+
+ ul.errors {
+ list-style-type: none;
+ padding: 0;
+ }
+
+ ul.errors li {
+ background: @color-critical;
+ font-weight: bold;
+ padding: 0.5em 1em;
+ color: white;
+ }
+ }
+
+ input[type=text], input[type=password], textarea, select {
+ max-width: 36em;
+ min-width: 20em;
+ width: 100%;
+ }
+ input:not([type=submit]),
+ textarea,
+ select {
+ line-height: 2em;
+ height: 2.4em;
+ padding-left: 0.5em;
+ border-style: solid;
+ border-color: @gray-lighter;
+ border-width: 1px;
+ border-radius: 3px;
+ color: @text-color;
+
+ &:hover {
+ border-color: @gray;
+ }
+
+ &:focus, &:focus:hover {
+ border-color: @icinga-blue;
+ outline: none;
+ }
+ }
+ // duplicated from 40 -> ordering problem
+ input.validated {
+ background-color: fade(@color-ok, 30%);
+ border-color: @color-ok;
+ }
+ textarea {
+ height: unset;
+ min-height: 2.4em;
+ resize: vertical;
+ }
+
+ input.search {
+ background-image: url("../img/icons/search.png");
+ background-repeat: no-repeat;
+ padding-left: 2em;
+ }
+
+ select[multiple], select[multiple=multiple] {
+ height: auto;
+ }
+
+ select option {
+ height: 2em;
+ padding-top: 0.3em;
+ }
+
+ #_FAKE_SUBMIT {
+ position: absolute;
+ left: -100%;
+ }
+
+ select::-moz-focus-inner {
+ border: 0;
+ }
+
+ select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #000;
+ }
+
+ select[value=""] {
+ color: blue;
+ border: 1px solid #666;
+ // background-color: white;
+ }
+
+ select option {
+ color: inherit;
+ padding-left: 0.5em;
+ }
+
+ // default option
+ select option[value=""] {
+ // color: #aaa;
+ }
+
+ fieldset {
+ margin: 0;
+ min-width: 36em;
+
+ padding: 0 0 1.5em 0;
+ border: none;
+
+ legend {
+ margin: 0 0 0.5em 0;
+ font-size: 1em;
+ border-bottom: 1px solid @gray-light;
+ font-weight: bold;
+ display: block;
+ width: 100%;
+ padding-left: 1em;
+ line-height: 2em;
+ cursor: pointer;
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+
+ &:hover {
+ border-color: @text-color;
+ }
+
+ &::before {
+ // icon: down-dir
+ font-family: 'ifont';
+ content: '\e81d';
+ margin-left: -1em;
+ padding-top: 0;
+ float: left;
+ color: inherit;
+ }
+ }
+
+ &.collapsed {
+ dl, dd, dt, ul, div {
+ display: none;
+ }
+
+ legend {
+ margin: 0;
+ }
+ legend::before {
+ // icon: right-dir
+ content: '\e820';
+ }
+
+ margin-bottom: 0.2em;
+ padding-bottom: 0;
+ }
+ }
+}
+
+#layout.minimal-layout div.content form.gipfl-form {
+ dt, dd {
+ display: block;
+ width: auto;
+ }
+
+ dt label {
+ color: @text-color;
+ }
+
+ fieldset {
+ min-width: unset;
+ }
+
+ input[type=text], input[type=password], textarea, select {
+ max-width: unset;
+ min-width: unset;
+ border-color: @gray-light;
+ }
+
+ dd.active {
+ input[type=text], input[type=password], textarea, select {
+ border-color: @icinga-blue;
+ }
+ }
+
+ fieldset.collapsed {
+ dd, dt, ul, div {
+ display: none;
+ }
+ }
+}
+
diff --git a/vendor/gipfl/web/public/css/42-director-extensible-set.less b/vendor/gipfl/web/public/css/42-director-extensible-set.less
new file mode 100644
index 0000000..c5380cd
--- /dev/null
+++ b/vendor/gipfl/web/public/css/42-director-extensible-set.less
@@ -0,0 +1,33 @@
+#layout.minimal-layout div.content form.gipfl-form {
+ ul.extensible-set {
+ max-width: unset;
+ border: 1px solid @gray-light;
+ }
+
+ dd.active ul.extensible-set {
+ border: 1px solid @icinga-blue;
+
+ input[type=submit]:first-of-type {
+ border-width: 1px;
+ }
+ }
+
+ dd input.related-action[type='submit'] {
+ display: none;
+ }
+
+ dd.active li.active input.related-action[type='submit'] {
+ display: inline-block;
+ }
+
+ dd.active ul.extensible-set, ul.extensible-set.sortable {
+ input[type=text], select {
+ width: 100%;
+ }
+
+ input[type=text] {
+ // background-color: white;
+ border: 1px solid white;
+ }
+ }
+}
diff --git a/vendor/gipfl/web/public/css/43-inline-form.less b/vendor/gipfl/web/public/css/43-inline-form.less
new file mode 100644
index 0000000..10d214c
--- /dev/null
+++ b/vendor/gipfl/web/public/css/43-inline-form.less
@@ -0,0 +1,29 @@
+form.gipfl-inline-form {
+ display: inline-block;
+ select {
+ width: auto;
+ min-width: unset;
+ max-width: unset;
+ border: none;
+ padding: 0 0.5em;
+ margin: 0;
+ line-height: unset;
+ box-sizing:border-box;
+ height: 1.5em;
+
+ &:hover {
+ border: none;
+ }
+
+ &:focus {
+ border: none;
+ }
+ }
+ input[type=submit] {
+ padding: 0 0.25em;
+ margin: 0 1em 0 0;
+ border: none;
+ line-height: unset;
+ box-sizing: border-box;
+ }
+}
diff --git a/vendor/gipfl/web/public/css/81-phpdiff.less b/vendor/gipfl/web/public/css/81-phpdiff.less
new file mode 100644
index 0000000..bc634b1
--- /dev/null
+++ b/vendor/gipfl/web/public/css/81-phpdiff.less
@@ -0,0 +1,97 @@
+.Differences {
+ width: 100%;
+ table-layout: fixed;
+ empty-cells: show;
+}
+
+.Differences thead {
+ display: none;
+}
+
+.Differences thead th {
+ text-align: left;
+ padding-left: 4 / 14 * 16em;
+}
+.Differences tbody th {
+ text-align: right;
+ width: 4em;
+ padding: 1px 2px;
+ border-right: 1px solid @gray-light;
+ background: @gray-lightest;
+ font-weight: normal;
+ vertical-align: top;
+}
+
+.Differences tbody td {
+ width: 50%;
+ .preformatted();
+ word-break: break-all;
+}
+
+@color-diff-ins: #bfb;
+@color-diff-del: #faa;
+@color-diff-changed-old: #fdd;
+@color-diff-changed-new: #efe;
+
+.DifferencesSideBySide {
+ ins, del {
+ text-decoration: none;
+ }
+
+ .ChangeInsert {
+ td.Left {
+ background: @gray-lighter;
+ }
+ td.Right {
+ background: @color-diff-ins;
+ }
+ }
+
+ .ChangeDelete {
+ td.Left {
+ background: @color-diff-del;
+ }
+ td.Right {
+ background: @gray-lighter;
+ }
+ }
+
+ .ChangeReplace {
+ td.Left {
+ background: @color-diff-changed-old;
+ del {
+ background: @color-diff-del;
+ }
+ }
+
+ td.Right {
+ background: @color-diff-changed-new;
+ ins {
+ background: @color-diff-ins;
+ }
+ }
+
+ }
+}
+
+.Differences .Skipped {
+ background: @gray-lightest;
+}
+
+.DifferencesInline .ChangeReplace .Left,
+.DifferencesInline .ChangeDelete .Left {
+ background: #fdd;
+}
+
+.DifferencesInline .ChangeReplace .Right,
+.DifferencesInline .ChangeInsert .Right {
+ background: #dfd;
+}
+
+.DifferencesInline .ChangeReplace ins {
+ background: #9e9;
+}
+
+.DifferencesInline .ChangeReplace del {
+ background: #e99;
+}
diff --git a/vendor/gipfl/web/public/js/module.js b/vendor/gipfl/web/public/js/module.js
new file mode 100644
index 0000000..83b19a5
--- /dev/null
+++ b/vendor/gipfl/web/public/js/module.js
@@ -0,0 +1,69 @@
+(function(window, $) {
+ 'use strict';
+
+ var Web = function () {
+ };
+
+ Web.prototype = {
+ initialize: function (icinga) {
+ this.icinga = icinga;
+ $(document).on('focus', 'form.gipfl-form input, form.gipfl-form textarea, form.gipfl-form select', this.formElementFocus);
+ $(document).on('click', '.gipfl-collapsible-control', this.toggleCollapsible);
+ },
+
+ toggleCollapsible: function (ev) {
+ var $toggle = $(ev.currentTarget);
+ var $collapsible = $toggle.parent();
+ $collapsible.toggleClass('collapsed');
+ },
+
+ formElementFocus: function (ev) {
+ var $input = $(ev.currentTarget);
+ if ($input.closest('form.editor').length) {
+ return;
+ }
+ var $set = $input.closest('.extensible-set');
+ if ($set.length) {
+ var $textInputs = $('input[type=text]', $set);
+ if ($textInputs.length > 1) {
+ $textInputs.not(':first').attr('tabIndex', '-1');
+ }
+ }
+
+ var $dd = $input.closest('dd');
+ if ($dd.attr('id') && $dd.attr('id').match(/button/)) {
+ return;
+ }
+ var $li = $input.closest('li');
+ var $dt = $dd.prev();
+ var $form = $dd.closest('form');
+
+ $form.find('dt, dd, dl, li').removeClass('active');
+ $li.addClass('active');
+ $dt.addClass('active');
+ $dd.addClass('active');
+ $dt.closest('dl').addClass('active');
+ },
+
+ highlightFormErrors: function ($container) {
+ $container.find('dd ul.errors').each(function (idx, ul) {
+ var $ul = $(ul);
+ var $dd = $ul.closest('dd');
+ var $dt = $dd.prev();
+
+ $dt.addClass('errors');
+ $dd.addClass('errors');
+ });
+ },
+
+ toggleFieldset: function (ev) {
+ ev.stopPropagation();
+ var $fieldset = $(ev.currentTarget).closest('fieldset');
+ $fieldset.toggleClass('collapsed');
+ this.fixFieldsetInfo($fieldset);
+ this.openedFieldsets[$fieldset.attr('id')] = ! $fieldset.hasClass('collapsed');
+ }
+ };
+
+ window.incubatorComponentLoader.addComponent(new Web());
+})(window, jQuery);
diff --git a/vendor/gipfl/web/src/Form.php b/vendor/gipfl/web/src/Form.php
new file mode 100644
index 0000000..e5e52f9
--- /dev/null
+++ b/vendor/gipfl/web/src/Form.php
@@ -0,0 +1,281 @@
+<?php
+
+namespace gipfl\Web;
+
+use Exception;
+use gipfl\Web\Form\Decorator\DdDtDecorator;
+use gipfl\Web\Form\Validator\AlwaysFailValidator;
+use gipfl\Web\Form\Validator\PhpSessionBasedCsrfTokenValidator;
+use gipfl\Web\Widget\Hint;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Contract\FormElement;
+use ipl\Html\Error;
+use ipl\Html\Form as iplForm;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\FormElement\HiddenElement;
+use ipl\Html\Html;
+use RuntimeException;
+use function array_key_exists;
+use function get_class;
+use function parse_str;
+
+class Form extends iplForm
+{
+ protected $formNameElementName = '__FORM_NAME';
+
+ protected $useCsrf = true;
+
+ protected $useFormName = true;
+
+ protected $defaultDecoratorClass = DdDtDecorator::class;
+
+ protected $formCssClasses = ['gipfl-form'];
+
+ /** @var boolean|null */
+ protected $hasBeenSubmitted;
+
+ public function ensureAssembled()
+ {
+ if ($this->hasBeenAssembled === false) {
+ if ($this->getRequest() === null) {
+ throw new RuntimeException('Cannot assemble a WebForm without a Request');
+ }
+ $this->registerGipflElementLoader();
+ $this->setupStyling();
+ parent::ensureAssembled();
+ $this->prepareWebForm();
+ }
+
+ return $this;
+ }
+
+ protected function registerGipflElementLoader()
+ {
+ $this->addElementLoader(__NAMESPACE__ . '\\Form\\Element');
+ }
+
+ public function setSubmitted($submitted = true)
+ {
+ $this->hasBeenSubmitted = (bool) $submitted;
+
+ return $this;
+ }
+
+ public function hasBeenSubmitted()
+ {
+ if ($this->hasBeenSubmitted === null) {
+ return parent::hasBeenSubmitted();
+ } else {
+ return $this->hasBeenSubmitted;
+ }
+ }
+
+ public function disableCsrf()
+ {
+ $this->useCsrf = false;
+
+ return $this;
+ }
+
+ public function doNotCheckFormName()
+ {
+ $this->useFormName = false;
+
+ return $this;
+ }
+
+ protected function prepareWebForm()
+ {
+ if ($this->hasElement($this->formNameElementName)) {
+ return; // Called twice
+ }
+ if ($this->useFormName) {
+ $this->addFormNameElement();
+ }
+ if ($this->useCsrf && $this->getMethod() === 'POST') {
+ $this->addCsrfElement();
+ }
+ }
+
+ protected function getUniqueFormName()
+ {
+ return get_class($this);
+ }
+
+ protected function addFormNameElement()
+ {
+ $element = new HiddenElement($this->formNameElementName, [
+ 'value' => $this->getUniqueFormName(),
+ 'ignore' => true,
+ ]);
+ $this->prepend($element);
+ $this->registerElement($element);
+ }
+
+ public function addHidden($name, $value = null, $attributes = [])
+ {
+ if (is_array($value) && empty($attributes)) {
+ $attributes = $value;
+ $value = null;
+ } elseif ($value === null && is_scalar($attributes)) {
+ $value = $attributes;
+ $attributes = [];
+ }
+ if ($value !== null) {
+ $attributes['value'] = $value;
+ }
+ $element = new HiddenElement($name, $attributes);
+ $this->prepend($element);
+ $this->registerElement($element);
+ }
+
+ public function registerElement(FormElement $element)
+ {
+ $idPrefix = '';
+ if ($element instanceof BaseHtmlElement) {
+ if (! $element->getAttributes()->has('id')) {
+ $element->addAttributes(['id' => $idPrefix . $element->getName()]);
+ }
+ }
+
+ return parent::registerElement($element);
+ }
+
+ public function setElementValue($element, $value)
+ {
+ $this->wantFormElement($element)->setValue($value);
+ }
+
+ public function getElementValue($elementName, $defaultValue = null)
+ {
+ $value = $this->getElement($elementName)->getValue();
+ if ($value === null) {
+ return $defaultValue;
+ } else {
+ return $value;
+ }
+ }
+
+ public function hasElementValue($elementName)
+ {
+ if ($this->hasElement($elementName)) {
+ return $this->getElement($elementName)->hasValue();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @param $element
+ * @return FormElement
+ */
+ protected function wantFormElement($element)
+ {
+ if ($element instanceof BaseFormElement) {
+ return $element;
+ } else {
+ return $this->getElement($element);
+ }
+ }
+
+ public function triggerElementError($element, $message, ...$params)
+ {
+ if (! empty($params)) {
+ $message = Html::sprintf($message, $params);
+ }
+
+ $element = $this->wantFormElement($this->getElement($element));
+ $element->addValidators([
+ new AlwaysFailValidator(['message' => $message])
+ ]);
+ }
+
+ protected function setupStyling()
+ {
+ $this->setSeparator("\n");
+ $this->addAttributes(['class' => $this->formCssClasses]);
+ if ($this->defaultDecoratorClass !== null) {
+ $this->setDefaultElementDecorator(new $this->defaultDecoratorClass);
+ }
+ }
+
+ protected function addCsrfElement()
+ {
+ $element = new HiddenElement('__CSRF__', [
+ 'ignore' => true,
+ ]);
+ $element->setValidators([
+ new PhpSessionBasedCsrfTokenValidator()
+ ]);
+ // prepend / register -> avoid decorator
+ $this->prepend($element);
+ $this->registerElement($element);
+ if ($this->hasBeenSent()) {
+ if (! $element->isValid()) {
+ $element->setValue(PhpSessionBasedCsrfTokenValidator::generateCsrfValue());
+ }
+ } else {
+ $element->setValue(PhpSessionBasedCsrfTokenValidator::generateCsrfValue());
+ }
+ }
+
+ public function getSentValue($name, $default = null)
+ {
+ $params = $this->getSentValues();
+
+ if (array_key_exists($name, $params)) {
+ return $params[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ public function getSentValues()
+ {
+ $request = $this->getRequest();
+ if ($request === null) {
+ throw new RuntimeException(
+ "It's impossible to access SENT values with no request"
+ );
+ }
+
+ if ($request->getMethod() === 'POST') {
+ $params = $request->getParsedBody();
+ } elseif ($this->getMethod() === 'GET') {
+ parse_str($request->getUri()->getQuery(), $params);
+ } else {
+ $params = [];
+ }
+
+ return $params;
+ }
+
+ protected function onError()
+ {
+ $messages = $this->getMessages();
+ if (empty($messages)) {
+ return;
+ }
+ $errors = [];
+ foreach ($this->getMessages() as $message) {
+ if ($message instanceof Exception) {
+ $this->prepend(Error::show($message));
+ } else {
+ $errors[] = $message;
+ }
+ }
+ if (! empty($errors)) {
+ $this->prepend(Hint::error(implode(', ', $errors)));
+ }
+ }
+
+ public function hasBeenSent()
+ {
+ if (parent::hasBeenSent()) {
+ return !$this->useFormName || $this->getSentValue($this->formNameElementName)
+ === $this->getUniqueFormName();
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php b/vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php
new file mode 100644
index 0000000..e5deae4
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Decorator/DdDtDecorator.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace gipfl\Web\Form\Decorator;
+
+use gipfl\Web\HtmlHelper;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\FormDecorator\DecoratorInterface;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+
+class DdDtDecorator extends BaseHtmlElement implements DecoratorInterface
+{
+ const CSS_CLASS_ELEMENT_HAS_ERRORS = 'gipfl-form-element-has-errors';
+
+ const CSS_CLASS_ELEMENT_ERRORS = 'gipfl-form-element-errors';
+
+ const CSS_CLASS_DESCRIPTION = 'gipfl-element-description';
+
+ protected $tag = 'dl';
+
+ protected $dt;
+
+ protected $dd;
+
+ /** @var BaseFormElement */
+ protected $element;
+
+ /** @var HtmlDocument */
+ protected $elementDoc;
+
+ /**
+ * @param BaseFormElement $element
+ * @return static
+ */
+ public function decorate(BaseFormElement $element)
+ {
+ $decorator = clone($this);
+ $decorator->element = $element;
+ $decorator->elementDoc = new HtmlDocument();
+ $decorator->elementDoc->add($element);
+ // if (! $element instanceof HiddenElement) {
+ $element->prependWrapper($decorator);
+
+ return $decorator;
+ }
+
+ protected function prepareLabel()
+ {
+ $element = $this->element;
+ $label = $element->getLabel();
+ if ($label === null || \strlen($label) === 0) {
+ return null;
+ }
+
+ // Set HTML element.id to element name unless defined
+ if ($element->getAttributes()->has('id')) {
+ $attributes = ['for' => $element->getAttributes()->get('id')->getValue()];
+ } else {
+ $attributes = null;
+ }
+
+ if ($element->isRequired()) {
+ $label = [$label, Html::tag('span', ['aria-hidden' => 'true'], '*')];
+ }
+
+ return Html::tag('label', $attributes, $label);
+ }
+
+ public function getAttributes()
+ {
+ $attributes = parent::getAttributes();
+
+ // TODO: only when sent?!
+ if ($this->element->hasBeenValidated() && ! $this->element->isValid()) {
+ HtmlHelper::addClassOnce($attributes, static::CSS_CLASS_ELEMENT_HAS_ERRORS);
+ }
+
+ return $attributes;
+ }
+
+ protected function prepareDescription()
+ {
+ if ($this->element) {
+ $description = $this->element->getDescription();
+ if ($description !== null && \strlen($description)) {
+ return Html::tag('p', ['class' => static::CSS_CLASS_DESCRIPTION], $description);
+ }
+ }
+
+ return null;
+ }
+
+ protected function prepareErrors()
+ {
+ $errors = [];
+ foreach ($this->element->getMessages() as $message) {
+ $errors[] = Html::tag('li', $message);
+ }
+
+ if (empty($errors)) {
+ return null;
+ } else {
+ return Html::tag('ul', ['class' => static::CSS_CLASS_ELEMENT_ERRORS], $errors);
+ }
+ }
+
+ public function add($content)
+ {
+ // Our wrapper implementation automatically adds the wrapped element but
+ // we already do so in assemble()
+ if ($content !== $this->element) {
+ parent::add($content);
+ }
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ $this->add([$this->dt(), $this->dd()]);
+ }
+
+ public function getElementDocument()
+ {
+ return $this->elementDoc;
+ }
+
+ public function dt()
+ {
+ if ($this->dt === null) {
+ $this->dt = Html::tag('dt', null, $this->prepareLabel());
+ }
+
+ return $this->dt;
+ }
+
+ /**
+ * @return \ipl\Html\HtmlElement
+ */
+ public function dd()
+ {
+ if ($this->dd === null) {
+ $this->dd = Html::tag('dd', null, [
+ $this->getElementDocument(),
+ $this->prepareErrors(),
+ $this->prepareDescription()
+ ]);
+ }
+
+ return $this->dd;
+ }
+
+ public function __destruct()
+ {
+ $this->wrapper = null;
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Element/Boolean.php b/vendor/gipfl/web/src/Form/Element/Boolean.php
new file mode 100644
index 0000000..dc5f85f
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Element/Boolean.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use gipfl\Translation\TranslationHelper;
+use ipl\Html\FormElement\SelectElement;
+
+class Boolean extends SelectElement
+{
+ use TranslationHelper;
+
+ public function __construct($name, $attributes = null)
+ {
+ parent::__construct($name, $attributes);
+ $options = [
+ 'y' => $this->translate('Yes'),
+ 'n' => $this->translate('No'),
+ ];
+ if (! $this->isRequired()) {
+ $options = [
+ null => $this->translate('- please choose -'),
+ ] + $options;
+ }
+
+ $this->setOptions($options);
+ }
+
+ public function setValue($value)
+ {
+ if ($value === 'y' || $value === true) {
+ return parent::setValue('y');
+ } elseif ($value === 'n' || $value === false) {
+ return parent::setValue('n');
+ }
+
+ // Hint: this will fail
+ return parent::setValue($value);
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Element/MultiSelect.php b/vendor/gipfl/web/src/Form/Element/MultiSelect.php
new file mode 100644
index 0000000..07e2e9e
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Element/MultiSelect.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use ipl\Html\Attributes;
+use ipl\Html\FormElement\SelectElement;
+
+class MultiSelect extends SelectElement
+{
+ protected $value = [];
+
+ public function __construct($name, $attributes = null)
+ {
+ // Make sure we set value last as it depends on options
+ if (isset($attributes['value'])) {
+ $value = $attributes['value'];
+ unset($attributes['value']);
+ $attributes['value'] = $value;
+ }
+
+ parent::__construct($name, $attributes);
+
+ $this->getAttributes()->add('multiple', true);
+ }
+
+ protected function registerValueCallback(Attributes $attributes)
+ {
+ $attributes->registerAttributeCallback(
+ 'value',
+ null,
+ [$this, 'setValue']
+ );
+ }
+
+ public function getNameAttribute()
+ {
+ return $this->getName() . '[]';
+ }
+
+ public function setValue($value)
+ {
+ if (empty($value)) { // null, '', []
+ $values = [];
+ } else {
+ $values = (array) $value;
+ }
+ $invalid = [];
+ foreach ($values as $val) {
+ if ($option = $this->getOption($val)) {
+ if ($option->getAttributes()->has('disabled')) {
+ $invalid[] = $val;
+ }
+ } else {
+ $invalid[] = $val;
+ }
+ }
+ if (count($invalid) > 0) {
+ $this->failForValues($invalid);
+ return $this;
+ }
+
+ $this->value = $values;
+ $this->valid = null;
+ $this->updateSelection();
+
+ return $this;
+ }
+
+ protected function failForValues($values)
+ {
+ $this->valid = false;
+ if (count($values) === 1) {
+ $value = array_shift($values);
+ $this->addMessage("'$value' is not allowed here");
+ } else {
+ $valueString = implode("', '", $values);
+ $this->addMessage("'$valueString' are not allowed here");
+ }
+ }
+
+ public function validate()
+ {
+ /**
+ * @TODO(lippserd): {@link SelectElement::validate()} doesn't work here because isset checks fail with
+ * illegal offset type errors since our value is an array. It would make sense to decouple the classes to
+ * avoid having to copy code from the base class.
+ * Also note that {@see setValue()} already performs most of the validation.
+ */
+ if ($this->isRequired() && empty($this->getValue())) {
+ $this->valid = false;
+ } else {
+ /**
+ * Copied from {@link \ipl\Html\BaseHtmlElement::validate()}.
+ */
+ $this->valid = $this->getValidators()->isValid($this->getValue());
+ $this->addMessages($this->getValidators()->getMessages());
+ }
+ }
+
+ public function updateSelection()
+ {
+ foreach ($this->options as $value => $option) {
+ if (in_array($value, $this->value)) {
+ $option->getAttributes()->add('selected', true);
+ } else {
+ $option->getAttributes()->remove('selected');
+ }
+ }
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ foreach ($this->options as $option) {
+ $this->add($option);
+ }
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Element/Password.php b/vendor/gipfl/web/src/Form/Element/Password.php
new file mode 100644
index 0000000..b6f148e
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Element/Password.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use ipl\Html\FormElement\TextElement;
+
+class Password extends TextElement
+{
+ // TODO
+ protected $type = 'password';
+}
diff --git a/vendor/gipfl/web/src/Form/Element/TextWithActionButton.php b/vendor/gipfl/web/src/Form/Element/TextWithActionButton.php
new file mode 100644
index 0000000..13ebfb8
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Element/TextWithActionButton.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace gipfl\Web\Form\Element;
+
+use gipfl\Web\Form\Decorator\DdDtDecorator;
+use ipl\Html\Attributes;
+use ipl\Html\Form;
+use ipl\Html\FormElement\SubmitElement;
+use ipl\Html\FormElement\TextElement;
+
+class TextWithActionButton
+{
+ /** @var SubmitElement */
+ protected $button;
+
+ /** @var TextElement */
+ protected $element;
+
+ protected $buttonSuffix = '_related_button';
+
+ /** @var string */
+ protected $elementName;
+
+ /** @var array|Attributes */
+ protected $elementAttributes;
+
+ /** @var array|Attributes */
+ protected $buttonAttributes;
+
+ protected $elementClasses = ['input-with-button'];
+
+ protected $buttonClasses = ['input-element-related-button'];
+
+ /**
+ * TextWithActionButton constructor.
+ * @param string $elementName
+ * @param array|Attributes $elementAttributes
+ * @param array|Attributes $buttonAttributes
+ */
+ public function __construct($elementName, $elementAttributes, $buttonAttributes)
+ {
+ $this->elementName = $elementName;
+ $this->elementAttributes = $elementAttributes;
+ $this->buttonAttributes = $buttonAttributes;
+ }
+
+ public function addToForm(Form $form)
+ {
+ $button = $this->getButton();
+ $form->registerElement($button);
+ $element = $this->getElement();
+ $form->addElement($element);
+ /** @var DdDtDecorator $deco */
+ $deco = $element->getWrapper();
+ if ($deco instanceof DdDtDecorator) {
+ $deco->addAttributes(['position' => 'relative'])->getElementDocument()->add($button);
+ }
+ }
+
+ public function getElement()
+ {
+ if ($this->element === null) {
+ $this->element = $this->createTextElement(
+ $this->elementName,
+ $this->elementAttributes
+ );
+ }
+
+ return $this->element;
+ }
+
+ public function getButton()
+ {
+ if ($this->button === null) {
+ $this->button = $this->createSubmitElement(
+ $this->elementName . $this->buttonSuffix,
+ $this->buttonAttributes
+ );
+ }
+
+ return $this->button;
+ }
+
+ protected function createTextElement($name, $attributes = null)
+ {
+ $element = new TextElement($name, $attributes);
+ $element->addAttributes([
+ 'class' => $this->elementClasses,
+ ]);
+
+ return $element;
+ }
+
+ protected function createSubmitElement($name, $attributes = null)
+ {
+ $element = new SubmitElement($name, $attributes);
+ $element->addAttributes([
+ 'formnovalidate' => true,
+ 'class' => $this->buttonClasses,
+ ]);
+
+ return $element;
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php b/vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php
new file mode 100644
index 0000000..81885d6
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Feature/NextConfirmCancel.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace gipfl\Web\Form\Feature;
+
+use gipfl\Web\Form;
+use ipl\Html\DeferredText;
+use ipl\Html\FormElement\BaseFormElement;
+use ipl\Html\FormElement\SubmitElement;
+use ipl\Html\HtmlDocument;
+use ipl\Html\ValidHtml;
+
+class NextConfirmCancel
+{
+ /** @var SubmitElement */
+ protected $next;
+
+ /** @var SubmitElement */
+ protected $confirm;
+
+ /** @var SubmitElement */
+ protected $cancel;
+
+ protected $withNext;
+
+ protected $withNextContent;
+
+ protected $withConfirm;
+
+ protected $withConfirmContent;
+
+ protected $confirmFirst = true;
+
+ public function __construct(SubmitElement $next, SubmitElement $confirm, SubmitElement $cancel)
+ {
+ $this->next = $next;
+ $this->confirm = $confirm;
+ $this->cancel = $cancel;
+ $this->withNextContent = new HtmlDocument();
+ $this->withNext = new DeferredText(function () {
+ return $this->withNextContent;
+ });
+ $this->withNext->setEscaped();
+
+ $this->withConfirmContent = new HtmlDocument();
+ $this->withConfirm = new DeferredText(function () {
+ return $this->withConfirmContent;
+ });
+ $this->withConfirm->setEscaped();
+ }
+
+ public function showWithNext($content)
+ {
+ $this->withNextContent->add($content);
+ }
+
+ public function showWithConfirm($content)
+ {
+ $this->withConfirmContent->add($content);
+ }
+
+ /**
+ * @param ValidHtml $html
+ * @param array $found Internal parameter
+ * @return BaseFormElement[]
+ */
+ protected function pickFormElements(ValidHtml $html, &$found = [])
+ {
+ if ($html instanceof BaseFormElement) {
+ $found[] = $html;
+ } elseif ($html instanceof HtmlDocument) {
+ foreach ($html->getContent() as $content) {
+ $this->pickFormElements($content, $found);
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * @param string $label
+ * @param array $attributes
+ * @return SubmitElement
+ */
+ public static function buttonNext($label, $attributes = [])
+ {
+ return new SubmitElement('next', $attributes + [
+ 'label' => $label
+ ]);
+ }
+
+ /**
+ * @param string $label
+ * @param array $attributes
+ * @return SubmitElement
+ */
+ public static function buttonConfirm($label, $attributes = [])
+ {
+ return new SubmitElement('submit', $attributes + [
+ 'label' => $label
+ ]);
+ }
+
+ /**
+ * @param string $label
+ * @param array $attributes
+ * @return SubmitElement
+ */
+ public static function buttonCancel($label, $attributes = [])
+ {
+ return new SubmitElement('cancel', $attributes + [
+ 'label' => $label
+ ]);
+ }
+
+ public function addToForm(Form $form)
+ {
+ $cancel = $this->cancel;
+ $confirm = $this->confirm;
+ $next = $this->next;
+ if ($form->hasBeenSent()) {
+ $form->add($this->withConfirm);
+ if ($this->confirmFirst) {
+ $form->addElement($confirm);
+ $form->addElement($cancel);
+ } else {
+ $form->addElement($cancel);
+ $form->addElement($confirm);
+ }
+ if ($cancel->hasBeenPressed()) {
+ $this->withConfirmContent = new HtmlDocument();
+ // HINT: we might also want to redirect on cancel and stop here,
+ // but currently we have no Response
+ $form->setSubmitted(false);
+ $form->remove($confirm);
+ $form->remove($cancel);
+ $form->add($next);
+ $form->setSubmitButton($next);
+ } else {
+ $form->setSubmitButton($confirm);
+ $form->remove($next);
+ foreach ($this->pickFormElements($this->withConfirmContent) as $element) {
+ $form->registerElement($element);
+ }
+ }
+ } else {
+ $form->add($this->withNext);
+ foreach ($this->pickFormElements($this->withNextContent) as $element) {
+ $form->registerElement($element);
+ }
+ $form->addElement($next);
+ }
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php b/vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php
new file mode 100644
index 0000000..6fee92f
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Validator/AlwaysFailValidator.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace gipfl\Web\Form\Validator;
+
+class AlwaysFailValidator extends SimpleValidator
+{
+ public function isValid($value)
+ {
+ $message = $this->getSetting('message');
+ if ($message) {
+ $this->addMessage($message);
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php b/vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php
new file mode 100644
index 0000000..2f08c6c
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Validator/PhpSessionBasedCsrfTokenValidator.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace gipfl\Web\Form\Validator;
+
+class PhpSessionBasedCsrfTokenValidator extends SimpleValidator
+{
+ public function isValid($value)
+ {
+ if (strpos($value, '|') === false) {
+ return false;
+ }
+
+ list($seed, $token) = \explode('|', $value, 2);
+
+ if (! \is_numeric($seed)) {
+ return false;
+ }
+
+ if ($token === \hash('sha256', \session_id() . $seed)) {
+ return true;
+ } else {
+ $this->addMessage('An invalid CSRF token has been submitted');
+ return false;
+ }
+ }
+
+ public static function generateCsrfValue()
+ {
+ $seed = \mt_rand();
+ $token = \hash('sha256', \session_id() . $seed);
+
+ return \sprintf('%s|%s', $seed, $token);
+ }
+}
diff --git a/vendor/gipfl/web/src/Form/Validator/SimpleValidator.php b/vendor/gipfl/web/src/Form/Validator/SimpleValidator.php
new file mode 100644
index 0000000..e06a10f
--- /dev/null
+++ b/vendor/gipfl/web/src/Form/Validator/SimpleValidator.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace gipfl\Web\Form\Validator;
+
+use ipl\Stdlib\Contract\Validator;
+use ipl\Stdlib\Messages;
+
+abstract class SimpleValidator implements Validator
+{
+ use Messages;
+
+ protected $settings = [];
+
+ public function __construct(array $settings = [])
+ {
+ $this->settings = $settings;
+ }
+
+ public function getSetting($name, $default = null)
+ {
+ if (array_key_exists($name, $this->settings)) {
+ return $this->settings[$name];
+ } else {
+ return $default;
+ }
+ }
+}
diff --git a/vendor/gipfl/web/src/HtmlHelper.php b/vendor/gipfl/web/src/HtmlHelper.php
new file mode 100644
index 0000000..19862f8
--- /dev/null
+++ b/vendor/gipfl/web/src/HtmlHelper.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace gipfl\Web;
+
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+
+abstract class HtmlHelper
+{
+ public static function elementHasClass(BaseHtmlElement $element, $class)
+ {
+ return static::classIsSet($element->getAttributes(), $class);
+ }
+
+ public static function addClassOnce(Attributes $attributes, $class)
+ {
+ if (! HtmlHelper::classIsSet($attributes, $class)) {
+ $attributes->add('class', $class);
+ }
+ }
+
+ public static function classIsSet(Attributes $attributes, $class)
+ {
+ $classes = $attributes->get('class');
+
+ return \is_array($classes) && in_array($class, $classes)
+ || \is_string($classes) && $classes === $class;
+ }
+}
diff --git a/vendor/gipfl/web/src/InlineForm.php b/vendor/gipfl/web/src/InlineForm.php
new file mode 100644
index 0000000..fd6b301
--- /dev/null
+++ b/vendor/gipfl/web/src/InlineForm.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace gipfl\Web;
+
+class InlineForm extends Form
+{
+ protected $defaultDecoratorClass = null;
+
+ protected $formCssClasses = ['gipfl-form', 'gipfl-inline-form'];
+}
diff --git a/vendor/gipfl/web/src/Table/NameValueTable.php b/vendor/gipfl/web/src/Table/NameValueTable.php
new file mode 100644
index 0000000..7227de7
--- /dev/null
+++ b/vendor/gipfl/web/src/Table/NameValueTable.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace gipfl\Web\Table;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Table;
+
+class NameValueTable extends Table
+{
+ protected $defaultAttributes = ['class' => 'gipfl-name-value-table'];
+
+ public static function create($pairs = [])
+ {
+ $self = new static;
+ $self->addNameValuePairs($pairs);
+
+ return $self;
+ }
+
+ public function createNameValueRow($name, $value)
+ {
+ return $this::tr([$this::th($name), $this::wantTd($value)]);
+ }
+
+ public function addNameValueRow($name, $value)
+ {
+ return $this->add($this->createNameValueRow($name, $value));
+ }
+
+ public function addNameValuePairs($pairs)
+ {
+ foreach ($pairs as $name => $value) {
+ $this->addNameValueRow($name, $value);
+ }
+
+ return $this;
+ }
+
+ protected function wantTd($value)
+ {
+ if ($value instanceof BaseHtmlElement && $value->getTag() === 'td') {
+ return $value;
+ } else {
+ return $this::td($value);
+ }
+ }
+}
diff --git a/vendor/gipfl/web/src/Widget/CollapsibleList.php b/vendor/gipfl/web/src/Widget/CollapsibleList.php
new file mode 100644
index 0000000..0df8234
--- /dev/null
+++ b/vendor/gipfl/web/src/Widget/CollapsibleList.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace gipfl\Web\Widget;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use InvalidArgumentException;
+use LogicException;
+use function count;
+
+class CollapsibleList extends BaseHtmlElement
+{
+ protected $tag = 'ul';
+
+ protected $defaultAttributes = [
+ 'class' => 'gipfl-collapsible'
+ ];
+
+ protected $defaultListAttributes;
+
+ protected $defaultSectionAttributes;
+
+ protected $items = [];
+
+ public function __construct($items = [], $listAttributes = null)
+ {
+ if ($listAttributes !== null) {
+ $this->defaultListAttributes = $listAttributes;
+ }
+ foreach ($items as $title => $item) {
+ $this->addItem($title, $item);
+ }
+ }
+
+ public function addItem($title, $content)
+ {
+ if ($this->hasItem($title)) {
+ throw new LogicException("Cannot add item with title '$title' twice");
+ }
+ $item = Html::tag('li', [
+ Html::tag('a', ['href' => '#', 'class' => 'gipfl-collapsible-control'], $title),
+ $content
+ ]);
+
+ if (count($this->items) > 0) {
+ $item->getAttributes()->add('class', 'collapsed');
+ }
+ $this->items[$title] = $item;
+ }
+
+ public function hasItem($title)
+ {
+ return isset($this->items[$title]);
+ }
+
+ public function getItem($name)
+ {
+ if (isset($this->items[$name])) {
+ return $this->items[$name];
+ }
+
+ throw new InvalidArgumentException("There is no '$name' item in this list");
+ }
+
+ protected function assemble()
+ {
+ if ($this->defaultListAttributes) {
+ $this->addAttributes($this->defaultListAttributes);
+ }
+ foreach ($this->items as $item) {
+ $this->add($item);
+ }
+ }
+}
diff --git a/vendor/gipfl/web/src/Widget/ConfigDiff.php b/vendor/gipfl/web/src/Widget/ConfigDiff.php
new file mode 100644
index 0000000..8ac366f
--- /dev/null
+++ b/vendor/gipfl/web/src/Widget/ConfigDiff.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace gipfl\Web\Widget;
+
+use Diff;
+use ipl\Html\ValidHtml;
+use InvalidArgumentException;
+
+/**
+ * @deprecated - please use gipfl\Diff
+ */
+class ConfigDiff implements ValidHtml
+{
+ protected $a;
+
+ protected $b;
+
+ protected $diff;
+
+ protected $htmlRenderer = 'SideBySide';
+
+ protected $knownHtmlRenderers = [
+ 'SideBySide',
+ 'Inline',
+ ];
+
+ protected $knownTextRenderers = [
+ 'Context',
+ 'Unified',
+ ];
+
+ protected $vendorDir;
+
+ protected function __construct($a, $b)
+ {
+ $this->vendorDir = \dirname(\dirname(__DIR__)) . '/vendor';
+ require_once $this->vendorDir . '/php-diff/lib/Diff.php';
+
+ if (empty($a)) {
+ $this->a = [];
+ } else {
+ $this->a = explode("\n", (string) $a);
+ }
+
+ if (empty($b)) {
+ $this->b = [];
+ } else {
+ $this->b = explode("\n", (string) $b);
+ }
+
+ $options = [
+ 'context' => 5,
+ // 'ignoreWhitespace' => true,
+ // 'ignoreCase' => true,
+ ];
+ $this->diff = new Diff($this->a, $this->b, $options);
+ }
+
+ public function render()
+ {
+ return $this->renderHtml();
+ }
+
+ /**
+ * @return string
+ */
+ public function renderHtml()
+ {
+ return $this->diff->Render($this->getHtmlRenderer());
+ }
+
+ public function setHtmlRenderer($name)
+ {
+ if (in_array($name, $this->knownHtmlRenderers)) {
+ $this->htmlRenderer = $name;
+ } else {
+ throw new InvalidArgumentException("There is no known '$name' renderer");
+ }
+
+ return $this;
+ }
+
+ protected function getHtmlRenderer()
+ {
+ $filename = sprintf(
+ '%s/vendor/php-diff/lib/Diff/Renderer/Html/%s.php',
+ $this->vendorDir,
+ $this->htmlRenderer
+ );
+ require_once($filename);
+
+ $class = 'Diff_Renderer_Html_' . $this->htmlRenderer;
+
+ return new $class();
+ }
+
+ public function __toString()
+ {
+ return $this->renderHtml();
+ }
+
+ public static function create($a, $b)
+ {
+ return new static($a, $b);
+ }
+}
diff --git a/vendor/gipfl/web/src/Widget/Hint.php b/vendor/gipfl/web/src/Widget/Hint.php
new file mode 100644
index 0000000..785d9e4
--- /dev/null
+++ b/vendor/gipfl/web/src/Widget/Hint.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace gipfl\Web\Widget;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+
+class Hint extends BaseHtmlElement
+{
+ protected $tag = 'div';
+
+ protected $defaultAttributes = [
+ 'class' => 'gipfl-widget-hint'
+ ];
+
+ public function __construct($message, $class = 'ok', ...$params)
+ {
+ $this->addAttributes(['class' => $class]);
+ if (empty($params)) {
+ $this->setContent($message);
+ } else {
+ $this->setContent(Html::sprintf($message, ...$params));
+ }
+ }
+
+ public static function ok($message, ...$params)
+ {
+ return new static($message, 'ok', ...$params);
+ }
+
+ public static function info($message, ...$params)
+ {
+ return new static($message, 'info', ...$params);
+ }
+
+ public static function warning($message, ...$params)
+ {
+ return new static($message, 'warning', ...$params);
+ }
+
+ public static function error($message, ...$params)
+ {
+ return new static($message, 'error', ...$params);
+ }
+}
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php
new file mode 100644
index 0000000..d1eb9da
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * Diff
+ *
+ * A comprehensive library for generating differences between two strings
+ * in multiple formats (unified, side by side HTML etc)
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package Diff
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+class Diff
+{
+ /**
+ * @var array The "old" sequence to use as the basis for the comparison.
+ */
+ private $a = null;
+
+ /**
+ * @var array The "new" sequence to generate the changes for.
+ */
+ private $b = null;
+
+ /**
+ * @var array Array containing the generated opcodes for the differences between the two items.
+ */
+ private $groupedCodes = null;
+
+ /**
+ * @var array Associative array of the default options available for the diff class and their default value.
+ */
+ private $defaultOptions = array(
+ 'context' => 3,
+ 'ignoreNewLines' => false,
+ 'ignoreWhitespace' => false,
+ 'ignoreCase' => false
+ );
+
+ /**
+ * @var array Array of the options that have been applied for generating the diff.
+ */
+ private $options = array();
+
+ /**
+ * The constructor.
+ *
+ * @param array $a Array containing the lines of the first string to compare.
+ * @param array $b Array containing the lines for the second string to compare.
+ */
+ public function __construct($a, $b, $options=array())
+ {
+ $this->a = $a;
+ $this->b = $b;
+
+ if (is_array($options))
+ $this->options = array_merge($this->defaultOptions, $options);
+ else
+ $this->options = $this->defaultOptions;
+ }
+
+ /**
+ * Render a diff using the supplied rendering class and return it.
+ *
+ * @param object $renderer An instance of the rendering object to use for generating the diff.
+ * @return mixed The generated diff. Exact return value depends on the rendered.
+ */
+ public function render(Diff_Renderer_Abstract $renderer)
+ {
+ $renderer->diff = $this;
+ return $renderer->render();
+ }
+
+ /**
+ * Get a range of lines from $start to $end from the first comparison string
+ * and return them as an array. If no values are supplied, the entire string
+ * is returned. It's also possible to specify just one line to return only
+ * that line.
+ *
+ * @param int $start The starting number.
+ * @param int $end The ending number. If not supplied, only the item in $start will be returned.
+ * @return array Array of all of the lines between the specified range.
+ */
+ public function getA($start=0, $end=null)
+ {
+ if($start == 0 && $end === null) {
+ return $this->a;
+ }
+
+ if($end === null) {
+ $length = 1;
+ }
+ else {
+ $length = $end - $start;
+ }
+
+ return array_slice($this->a, $start, $length);
+
+ }
+
+ /**
+ * Get a range of lines from $start to $end from the second comparison string
+ * and return them as an array. If no values are supplied, the entire string
+ * is returned. It's also possible to specify just one line to return only
+ * that line.
+ *
+ * @param int $start The starting number.
+ * @param int $end The ending number. If not supplied, only the item in $start will be returned.
+ * @return array Array of all of the lines between the specified range.
+ */
+ public function getB($start=0, $end=null)
+ {
+ if($start == 0 && $end === null) {
+ return $this->b;
+ }
+
+ if($end === null) {
+ $length = 1;
+ }
+ else {
+ $length = $end - $start;
+ }
+
+ return array_slice($this->b, $start, $length);
+ }
+
+ /**
+ * Generate a list of the compiled and grouped opcodes for the differences between the
+ * two strings. Generally called by the renderer, this class instantiates the sequence
+ * matcher and performs the actual diff generation and return an array of the opcodes
+ * for it. Once generated, the results are cached in the diff class instance.
+ *
+ * @return array Array of the grouped opcodes for the generated diff.
+ */
+ public function getGroupedOpcodes()
+ {
+ if(!is_null($this->groupedCodes)) {
+ return $this->groupedCodes;
+ }
+
+ require_once dirname(__FILE__).'/Diff/SequenceMatcher.php';
+ $sequenceMatcher = new Diff_SequenceMatcher($this->a, $this->b, null, $this->options);
+ $this->groupedCodes = $sequenceMatcher->getGroupedOpcodes($this->options['context']);
+ return $this->groupedCodes;
+ }
+} \ No newline at end of file
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php
new file mode 100644
index 0000000..f63c3e7
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Abstract.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Abstract class for diff renderers in PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+abstract class Diff_Renderer_Abstract
+{
+ /**
+ * @var object Instance of the diff class that this renderer is generating the rendered diff for.
+ */
+ public $diff;
+
+ /**
+ * @var array Array of the default options that apply to this renderer.
+ */
+ protected $defaultOptions = array();
+
+ /**
+ * @var array Array containing the user applied and merged default options for the renderer.
+ */
+ protected $options = array();
+
+ /**
+ * The constructor. Instantiates the rendering engine and if options are passed,
+ * sets the options for the renderer.
+ *
+ * @param array $options Optionally, an array of the options for the renderer.
+ */
+ public function __construct(array $options = array())
+ {
+ $this->setOptions($options);
+ }
+
+ /**
+ * Set the options of the renderer to those supplied in the passed in array.
+ * Options are merged with the default to ensure that there aren't any missing
+ * options.
+ *
+ * @param array $options Array of options to set.
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+} \ No newline at end of file
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php
new file mode 100644
index 0000000..2fe9625
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Array.php
@@ -0,0 +1,230 @@
+<?php
+/**
+ * Base renderer for rendering HTML based diffs for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/../Abstract.php';
+
+class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
+{
+ /**
+ * @var array Array of the default options that apply to this renderer.
+ */
+ protected $defaultOptions = array(
+ 'tabSize' => 4
+ );
+
+ /**
+ * Render and return an array structure suitable for generating HTML
+ * based differences. Generally called by subclasses that generate a
+ * HTML based diff and return an array of the changes to show in the diff.
+ *
+ * @return array An array of the generated chances, suitable for presentation in HTML.
+ */
+ public function render()
+ {
+ // As we'll be modifying a & b to include our change markers,
+ // we need to get the contents and store them here. That way
+ // we're not going to destroy the original data
+ $a = $this->diff->getA();
+ $b = $this->diff->getB();
+
+ $changes = array();
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach($opCodes as $group) {
+ $blocks = array();
+ $lastTag = null;
+ $lastBlock = 0;
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+
+ if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
+ for($i = 0; $i < ($i2 - $i1); ++$i) {
+ $fromLine = $a[$i1 + $i];
+ $toLine = $b[$j1 + $i];
+
+ list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
+ if($start != 0 || $end != 0) {
+ $last = $end + strlen($fromLine);
+ $fromLine = substr_replace($fromLine, "\0", $start, 0);
+ $fromLine = substr_replace($fromLine, "\1", $last + 1, 0);
+ $last = $end + strlen($toLine);
+ $toLine = substr_replace($toLine, "\0", $start, 0);
+ $toLine = substr_replace($toLine, "\1", $last + 1, 0);
+ $a[$i1 + $i] = $fromLine;
+ $b[$j1 + $i] = $toLine;
+ }
+ }
+ }
+
+ if($tag != $lastTag) {
+ $blocks[] = array(
+ 'tag' => $tag,
+ 'base' => array(
+ 'offset' => $i1,
+ 'lines' => array()
+ ),
+ 'changed' => array(
+ 'offset' => $j1,
+ 'lines' => array()
+ )
+ );
+ $lastBlock = count($blocks)-1;
+ }
+
+ $lastTag = $tag;
+
+ if($tag == 'equal') {
+ $lines = array_slice($a, $i1, ($i2 - $i1));
+ $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
+ $lines = array_slice($b, $j1, ($j2 - $j1));
+ $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
+ }
+ else {
+ if($tag == 'replace' || $tag == 'delete') {
+ $lines = array_slice($a, $i1, ($i2 - $i1));
+ $lines = $this->formatLines($lines);
+ $lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
+ $blocks[$lastBlock]['base']['lines'] += $lines;
+ }
+
+ if($tag == 'replace' || $tag == 'insert') {
+ $lines = array_slice($b, $j1, ($j2 - $j1));
+ $lines = $this->formatLines($lines);
+ $lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
+ $blocks[$lastBlock]['changed']['lines'] += $lines;
+ }
+ }
+ }
+ $changes[] = $blocks;
+ }
+ return $changes;
+ }
+
+ /**
+ * Given two strings, determine where the changes in the two strings
+ * begin, and where the changes in the two strings end.
+ *
+ * @param string $fromLine The first string.
+ * @param string $toLine The second string.
+ * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
+ */
+ private function getChangeExtent($fromLine, $toLine)
+ {
+ $start = 0;
+ $limit = min(strlen($fromLine), strlen($toLine));
+ while($start < $limit && $fromLine{$start} == $toLine{$start}) {
+ ++$start;
+ }
+ $end = -1;
+ $limit = $limit - $start;
+ while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) {
+ --$end;
+ }
+ return array(
+ $start,
+ $end + 1
+ );
+ }
+
+ /**
+ * Format a series of lines suitable for output in a HTML rendered diff.
+ * This involves replacing tab characters with spaces, making the HTML safe
+ * for output, ensuring that double spaces are replaced with &nbsp; etc.
+ *
+ * @param array $lines Array of lines to format.
+ * @return array Array of the formatted lines.
+ */
+ protected function formatLines($lines)
+ {
+ $lines = array_map(array($this, 'ExpandTabs'), $lines);
+ $lines = array_map(array($this, 'HtmlSafe'), $lines);
+ foreach($lines as &$line) {
+ $line = preg_replace_callback('# ( +)|^ #', array($this, 'fixSpaces'), $line);
+ }
+ return $lines;
+ }
+
+ /**
+ * Replace a string containing spaces with a HTML representation using &nbsp;.
+ *
+ * @param string[] $matches Array with preg matches.
+ * @return string The HTML representation of the string.
+ */
+ private function fixSpaces(array $matches)
+ {
+ $count = 0;
+
+ if (count($matches) > 1) {
+ $spaces = $matches[1];
+ $count = strlen($spaces);
+ }
+
+ if ($count == 0) {
+ return '';
+ }
+
+ $div = floor($count / 2);
+ $mod = $count % 2;
+ return str_repeat('&nbsp; ', $div).str_repeat('&nbsp;', $mod);
+ }
+
+ /**
+ * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
+ *
+ * @param string $line The containing tabs to convert.
+ * @return string The line with the tabs converted to spaces.
+ */
+ private function expandTabs($line)
+ {
+ return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
+ }
+
+ /**
+ * Make a string containing HTML safe for output on a page.
+ *
+ * @param string $string The string.
+ * @return string The string with the HTML characters replaced by entities.
+ */
+ private function htmlSafe($string)
+ {
+ return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
+ }
+}
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php
new file mode 100644
index 0000000..a37fec6
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Inline HTML diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/Array.php';
+
+class Diff_Renderer_Html_Inline extends Diff_Renderer_Html_Array
+{
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed inline (under each other)
+ *
+ * @return string The generated inline diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+ $html = '';
+ if(empty($changes)) {
+ return $html;
+ }
+
+ $html .= '<table class="Differences DifferencesInline">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th>Old</th>';
+ $html .= '<th>New</th>';
+ $html .= '<th>Differences</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ foreach($changes as $i => $blocks) {
+ // If this is a separate block, we're condensing code so output ...,
+ // indicating a significant portion of the code has been collapsed as
+ // it is the same
+ if($i > 0) {
+ $html .= '<tbody class="Skipped">';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<td>&nbsp;</td>';
+ $html .= '</tbody>';
+ }
+
+ foreach($blocks as $change) {
+ $html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
+ // Equal changes should be shown on both sides of the diff
+ if($change['tag'] == 'equal') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Left">'.$line.'</td>';
+ $html .= '</tr>';
+ }
+ }
+ // Added lines only on the right side
+ else if($change['tag'] == 'insert') {
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // Show deleted lines only on the left side
+ else if($change['tag'] == 'delete') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // Show modified lines on both sides
+ else if($change['tag'] == 'replace') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span></td>';
+ $html .= '</tr>';
+ }
+
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><span>'.$line.'</span></td>';
+ $html .= '</tr>';
+ }
+ }
+ $html .= '</tbody>';
+ }
+ }
+ $html .= '</table>';
+ return $html;
+ }
+}
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php
new file mode 100644
index 0000000..307af1c
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Side by Side HTML diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/Array.php';
+
+class Diff_Renderer_Html_SideBySide extends Diff_Renderer_Html_Array
+{
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed side by side.
+ *
+ * @return string The generated side by side diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+
+ $html = '';
+ if(empty($changes)) {
+ return $html;
+ }
+
+ $html .= '<table class="Differences DifferencesSideBySide">';
+ $html .= '<thead>';
+ $html .= '<tr>';
+ $html .= '<th colspan="2">Old Version</th>';
+ $html .= '<th colspan="2">New Version</th>';
+ $html .= '</tr>';
+ $html .= '</thead>';
+ foreach($changes as $i => $blocks) {
+ if($i > 0) {
+ $html .= '<tbody class="Skipped">';
+ $html .= '<th>&hellip;</th><td>&nbsp;</td>';
+ $html .= '<th>&hellip;</th><td>&nbsp;</td>';
+ $html .= '</tbody>';
+ }
+
+ foreach($blocks as $change) {
+ $html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
+ // Equal changes should be shown on both sides of the diff
+ if($change['tag'] == 'equal') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</span></td>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><span>'.$line.'</span>&nbsp;</span></td>';
+ $html .= '</tr>';
+ }
+ }
+ // Added lines only on the right side
+ else if($change['tag'] == 'insert') {
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Left">&nbsp;</td>';
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // Show deleted lines only on the left side
+ else if($change['tag'] == 'delete') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Right">&nbsp;</td>';
+ $html .= '</tr>';
+ }
+ }
+ // Show modified lines on both sides
+ else if($change['tag'] == 'replace') {
+ if(count($change['base']['lines']) >= count($change['changed']['lines'])) {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
+ if(!isset($change['changed']['lines'][$no])) {
+ $toLine = '&nbsp;';
+ $changedLine = '&nbsp;';
+ }
+ else {
+ $toLine = $change['base']['offset'] + $no + 1;
+ $changedLine = '<span>'.$change['changed']['lines'][$no].'</span>';
+ }
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right">'.$changedLine.'</td>';
+ $html .= '</tr>';
+ }
+ }
+ else {
+ foreach($change['changed']['lines'] as $no => $changedLine) {
+ if(!isset($change['base']['lines'][$no])) {
+ $fromLine = '&nbsp;';
+ $line = '&nbsp;';
+ }
+ else {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $line = '<span>'.$change['base']['lines'][$no].'</span>';
+ }
+ $html .= '<tr>';
+ $html .= '<th>'.$fromLine.'</th>';
+ $html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '<th>'.$toLine.'</th>';
+ $html .= '<td class="Right">'.$changedLine.'</td>';
+ $html .= '</tr>';
+ }
+ }
+ }
+ $html .= '</tbody>';
+ }
+ }
+ $html .= '</table>';
+ return $html;
+ }
+} \ No newline at end of file
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php
new file mode 100644
index 0000000..1200b01
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Context.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * Context diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/../Abstract.php';
+
+class Diff_Renderer_Text_Context extends Diff_Renderer_Abstract
+{
+ /**
+ * @var array Array of the different opcode tags and how they map to the context diff equivalent.
+ */
+ private $tagMap = array(
+ 'insert' => '+',
+ 'delete' => '-',
+ 'replace' => '!',
+ 'equal' => ' '
+ );
+
+ /**
+ * Render and return a context formatted (old school!) diff file.
+ *
+ * @return string The generated context diff.
+ */
+ public function render()
+ {
+ $diff = '';
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach($opCodes as $group) {
+ $diff .= "***************\n";
+ $lastItem = count($group)-1;
+ $i1 = $group[0][1];
+ $i2 = $group[$lastItem][2];
+ $j1 = $group[0][3];
+ $j2 = $group[$lastItem][4];
+
+ if($i2 - $i1 >= 2) {
+ $diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n";
+ }
+ else {
+ $diff .= '*** '.$i2." ****\n";
+ }
+
+ if($j2 - $j1 >= 2) {
+ $separator = '--- '.($j1 + 1).','.$j2." ----\n";
+ }
+ else {
+ $separator = '--- '.$j2." ----\n";
+ }
+
+ $hasVisible = false;
+ foreach($group as $code) {
+ if($code[0] == 'replace' || $code[0] == 'delete') {
+ $hasVisible = true;
+ break;
+ }
+ }
+
+ if($hasVisible) {
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'insert') {
+ continue;
+ }
+ $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetA($i1, $i2))."\n";
+ }
+ }
+
+ $hasVisible = false;
+ foreach($group as $code) {
+ if($code[0] == 'replace' || $code[0] == 'insert') {
+ $hasVisible = true;
+ break;
+ }
+ }
+
+ $diff .= $separator;
+
+ if($hasVisible) {
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'delete') {
+ continue;
+ }
+ $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetB($j1, $j2))."\n";
+ }
+ }
+ }
+ return $diff;
+ }
+} \ No newline at end of file
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php
new file mode 100644
index 0000000..e94d951
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Unified diff generator for PHP DiffLib.
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/../Abstract.php';
+
+class Diff_Renderer_Text_Unified extends Diff_Renderer_Abstract
+{
+ /**
+ * Render and return a unified diff.
+ *
+ * @return string The unified diff.
+ */
+ public function render()
+ {
+ $diff = '';
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach($opCodes as $group) {
+ $lastItem = count($group)-1;
+ $i1 = $group[0][1];
+ $i2 = $group[$lastItem][2];
+ $j1 = $group[0][3];
+ $j2 = $group[$lastItem][4];
+
+ if($i1 == 0 && $i2 == 0) {
+ $i1 = -1;
+ $i2 = -1;
+ }
+
+ $diff .= '@@ -'.($i1 + 1).','.($i2 - $i1).' +'.($j1 + 1).','.($j2 - $j1)." @@\n";
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'equal') {
+ $diff .= ' '.implode("\n ", $this->diff->GetA($i1, $i2))."\n";
+ }
+ else {
+ if($tag == 'replace' || $tag == 'delete') {
+ $diff .= '-'.implode("\n-", $this->diff->GetA($i1, $i2))."\n";
+ }
+
+ if($tag == 'replace' || $tag == 'insert') {
+ $diff .= '+'.implode("\n+", $this->diff->GetB($j1, $j2))."\n";
+ }
+ }
+ }
+ }
+ return $diff;
+ }
+} \ No newline at end of file
diff --git a/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php
new file mode 100644
index 0000000..a289e39
--- /dev/null
+++ b/vendor/gipfl/web/src/vendor/php-diff/lib/Diff/SequenceMatcher.php
@@ -0,0 +1,742 @@
+<?php
+/**
+ * Sequence matcher for Diff
+ *
+ * PHP version 5
+ *
+ * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package Diff
+ * @author Chris Boulton <chris.boulton@interspire.com>
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+class Diff_SequenceMatcher
+{
+ /**
+ * @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not.
+ */
+ private $junkCallback = null;
+
+ /**
+ * @var array The first sequence to compare against.
+ */
+ private $a = array();
+
+ /**
+ * @var array The second sequence.
+ */
+ private $b = array();
+
+ /**
+ * @var array Array of characters that are considered junk from the second sequence. Characters are the array key.
+ */
+ private $junkDict = array();
+
+ /**
+ * @var array Array of indices that do not contain junk elements.
+ */
+ private $b2j = array();
+
+ private $options = array();
+
+ private $defaultOptions = array(
+ 'ignoreNewLines' => false,
+ 'ignoreWhitespace' => false,
+ 'ignoreCase' => false
+ );
+
+ /**
+ * The constructor. With the sequences being passed, they'll be set for the
+ * sequence matcher and it will perform a basic cleanup & calculate junk
+ * elements.
+ *
+ * @param string|array $a A string or array containing the lines to compare against.
+ * @param string|array $b A string or array containing the lines to compare.
+ * @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters.
+ */
+ public function __construct($a, $b, $junkCallback=null, $options)
+ {
+ $this->a = array();
+ $this->b = array();
+ $this->junkCallback = $junkCallback;
+ $this->setOptions($options);
+ $this->setSequences($a, $b);
+ }
+
+ public function setOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+
+ /**
+ * Set the first and second sequences to use with the sequence matcher.
+ *
+ * @param string|array $a A string or array containing the lines to compare against.
+ * @param string|array $b A string or array containing the lines to compare.
+ */
+ public function setSequences($a, $b)
+ {
+ $this->setSeq1($a);
+ $this->setSeq2($b);
+ }
+
+ /**
+ * Set the first sequence ($a) and reset any internal caches to indicate that
+ * when calling the calculation methods, we need to recalculate them.
+ *
+ * @param string|array $a The sequence to set as the first sequence.
+ */
+ public function setSeq1($a)
+ {
+ if(!is_array($a)) {
+ $a = str_split($a);
+ }
+ if($a == $this->a) {
+ return;
+ }
+
+ $this->a= $a;
+ $this->matchingBlocks = null;
+ $this->opCodes = null;
+ }
+
+ /**
+ * Set the second sequence ($b) and reset any internal caches to indicate that
+ * when calling the calculation methods, we need to recalculate them.
+ *
+ * @param string|array $b The sequence to set as the second sequence.
+ */
+ public function setSeq2($b)
+ {
+ if(!is_array($b)) {
+ $b = str_split($b);
+ }
+ if($b == $this->b) {
+ return;
+ }
+
+ $this->b = $b;
+ $this->matchingBlocks = null;
+ $this->opCodes = null;
+ $this->fullBCount = null;
+ $this->chainB();
+ }
+
+ /**
+ * Generate the internal arrays containing the list of junk and non-junk
+ * characters for the second ($b) sequence.
+ */
+ private function chainB()
+ {
+ $length = count ($this->b);
+ $this->b2j = array();
+ $popularDict = array();
+
+ for($i = 0; $i < $length; ++$i) {
+ $char = $this->b[$i];
+ if(isset($this->b2j[$char])) {
+ if($length >= 200 && count($this->b2j[$char]) * 100 > $length) {
+ $popularDict[$char] = 1;
+ unset($this->b2j[$char]);
+ }
+ else {
+ $this->b2j[$char][] = $i;
+ }
+ }
+ else {
+ $this->b2j[$char] = array(
+ $i
+ );
+ }
+ }
+
+ // Remove leftovers
+ foreach(array_keys($popularDict) as $char) {
+ unset($this->b2j[$char]);
+ }
+
+ $this->junkDict = array();
+ if(is_callable($this->junkCallback)) {
+ foreach(array_keys($popularDict) as $char) {
+ if(call_user_func($this->junkCallback, $char)) {
+ $this->junkDict[$char] = 1;
+ unset($popularDict[$char]);
+ }
+ }
+
+ foreach(array_keys($this->b2j) as $char) {
+ if(call_user_func($this->junkCallback, $char)) {
+ $this->junkDict[$char] = 1;
+ unset($this->b2j[$char]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if a particular character is in the junk dictionary
+ * for the list of junk characters.
+ *
+ * @return boolean $b True if the character is considered junk. False if not.
+ */
+ private function isBJunk($b)
+ {
+ if(isset($this->juncDict[$b])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Find the longest matching block in the two sequences, as defined by the
+ * lower and upper constraints for each sequence. (for the first sequence,
+ * $alo - $ahi and for the second sequence, $blo - $bhi)
+ *
+ * Essentially, of all of the maximal matching blocks, return the one that
+ * startest earliest in $a, and all of those maximal matching blocks that
+ * start earliest in $a, return the one that starts earliest in $b.
+ *
+ * If the junk callback is defined, do the above but with the restriction
+ * that the junk element appears in the block. Extend it as far as possible
+ * by matching only junk elements in both $a and $b.
+ *
+ * @param int $alo The lower constraint for the first sequence.
+ * @param int $ahi The upper constraint for the first sequence.
+ * @param int $blo The lower constraint for the second sequence.
+ * @param int $bhi The upper constraint for the second sequence.
+ * @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size.
+ */
+ public function findLongestMatch($alo, $ahi, $blo, $bhi)
+ {
+ $a = $this->a;
+ $b = $this->b;
+
+ $bestI = $alo;
+ $bestJ = $blo;
+ $bestSize = 0;
+
+ $j2Len = array();
+ $nothing = array();
+
+ for($i = $alo; $i < $ahi; ++$i) {
+ $newJ2Len = array();
+ $jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing);
+ foreach($jDict as $jKey => $j) {
+ if($j < $blo) {
+ continue;
+ }
+ else if($j >= $bhi) {
+ break;
+ }
+
+ $k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1;
+ $newJ2Len[$j] = $k;
+ if($k > $bestSize) {
+ $bestI = $i - $k + 1;
+ $bestJ = $j - $k + 1;
+ $bestSize = $k;
+ }
+ }
+
+ $j2Len = $newJ2Len;
+ }
+
+ while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) &&
+ !$this->linesAreDifferent($bestI - 1, $bestJ - 1)) {
+ --$bestI;
+ --$bestJ;
+ ++$bestSize;
+ }
+
+ while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi &&
+ !$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
+ ++$bestSize;
+ }
+
+ while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) &&
+ !$this->isLineDifferent($bestI - 1, $bestJ - 1)) {
+ --$bestI;
+ --$bestJ;
+ ++$bestSize;
+ }
+
+ while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi &&
+ $this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
+ ++$bestSize;
+ }
+
+ return array(
+ $bestI,
+ $bestJ,
+ $bestSize
+ );
+ }
+
+ /**
+ * Check if the two lines at the given indexes are different or not.
+ *
+ * @param int $aIndex Line number to check against in a.
+ * @param int $bIndex Line number to check against in b.
+ * @return boolean True if the lines are different and false if not.
+ */
+ public function linesAreDifferent($aIndex, $bIndex)
+ {
+ $lineA = $this->a[$aIndex];
+ $lineB = $this->b[$bIndex];
+
+ if($this->options['ignoreWhitespace']) {
+ $replace = array("\t", ' ');
+ $lineA = str_replace($replace, '', $lineA);
+ $lineB = str_replace($replace, '', $lineB);
+ }
+
+ if($this->options['ignoreCase']) {
+ $lineA = strtolower($lineA);
+ $lineB = strtolower($lineB);
+ }
+
+ if($lineA != $lineB) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a nested set of arrays for all of the matching sub-sequences
+ * in the strings $a and $b.
+ *
+ * Each block contains the lower constraint of the block in $a, the lower
+ * constraint of the block in $b and finally the number of lines that the
+ * block continues for.
+ *
+ * @return array Nested array of the matching blocks, as described by the function.
+ */
+ public function getMatchingBlocks()
+ {
+ if(!empty($this->matchingBlocks)) {
+ return $this->matchingBlocks;
+ }
+
+ $aLength = count($this->a);
+ $bLength = count($this->b);
+
+ $queue = array(
+ array(
+ 0,
+ $aLength,
+ 0,
+ $bLength
+ )
+ );
+
+ $matchingBlocks = array();
+ while(!empty($queue)) {
+ list($alo, $ahi, $blo, $bhi) = array_pop($queue);
+ $x = $this->findLongestMatch($alo, $ahi, $blo, $bhi);
+ list($i, $j, $k) = $x;
+ if($k) {
+ $matchingBlocks[] = $x;
+ if($alo < $i && $blo < $j) {
+ $queue[] = array(
+ $alo,
+ $i,
+ $blo,
+ $j
+ );
+ }
+
+ if($i + $k < $ahi && $j + $k < $bhi) {
+ $queue[] = array(
+ $i + $k,
+ $ahi,
+ $j + $k,
+ $bhi
+ );
+ }
+ }
+ }
+
+ usort($matchingBlocks, array($this, 'tupleSort'));
+
+ $i1 = 0;
+ $j1 = 0;
+ $k1 = 0;
+ $nonAdjacent = array();
+ foreach($matchingBlocks as $block) {
+ list($i2, $j2, $k2) = $block;
+ if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) {
+ $k1 += $k2;
+ }
+ else {
+ if($k1) {
+ $nonAdjacent[] = array(
+ $i1,
+ $j1,
+ $k1
+ );
+ }
+
+ $i1 = $i2;
+ $j1 = $j2;
+ $k1 = $k2;
+ }
+ }
+
+ if($k1) {
+ $nonAdjacent[] = array(
+ $i1,
+ $j1,
+ $k1
+ );
+ }
+
+ $nonAdjacent[] = array(
+ $aLength,
+ $bLength,
+ 0
+ );
+
+ $this->matchingBlocks = $nonAdjacent;
+ return $this->matchingBlocks;
+ }
+
+ /**
+ * Return a list of all of the opcodes for the differences between the
+ * two strings.
+ *
+ * The nested array returned contains an array describing the opcode
+ * which includes:
+ * 0 - The type of tag (as described below) for the opcode.
+ * 1 - The beginning line in the first sequence.
+ * 2 - The end line in the first sequence.
+ * 3 - The beginning line in the second sequence.
+ * 4 - The end line in the second sequence.
+ *
+ * The different types of tags include:
+ * replace - The string from $i1 to $i2 in $a should be replaced by
+ * the string in $b from $j1 to $j2.
+ * delete - The string in $a from $i1 to $j2 should be deleted.
+ * insert - The string in $b from $j1 to $j2 should be inserted at
+ * $i1 in $a.
+ * equal - The two strings with the specified ranges are equal.
+ *
+ * @return array Array of the opcodes describing the differences between the strings.
+ */
+ public function getOpCodes()
+ {
+ if(!empty($this->opCodes)) {
+ return $this->opCodes;
+ }
+
+ $i = 0;
+ $j = 0;
+ $this->opCodes = array();
+
+ $blocks = $this->getMatchingBlocks();
+ foreach($blocks as $block) {
+ list($ai, $bj, $size) = $block;
+ $tag = '';
+ if($i < $ai && $j < $bj) {
+ $tag = 'replace';
+ }
+ else if($i < $ai) {
+ $tag = 'delete';
+ }
+ else if($j < $bj) {
+ $tag = 'insert';
+ }
+
+ if($tag) {
+ $this->opCodes[] = array(
+ $tag,
+ $i,
+ $ai,
+ $j,
+ $bj
+ );
+ }
+
+ $i = $ai + $size;
+ $j = $bj + $size;
+
+ if($size) {
+ $this->opCodes[] = array(
+ 'equal',
+ $ai,
+ $i,
+ $bj,
+ $j
+ );
+ }
+ }
+ return $this->opCodes;
+ }
+
+ /**
+ * Return a series of nested arrays containing different groups of generated
+ * opcodes for the differences between the strings with up to $context lines
+ * of surrounding content.
+ *
+ * Essentially what happens here is any big equal blocks of strings are stripped
+ * out, the smaller subsets of changes are then arranged in to their groups.
+ * This means that the sequence matcher and diffs do not need to include the full
+ * content of the different files but can still provide context as to where the
+ * changes are.
+ *
+ * @param int $context The number of lines of context to provide around the groups.
+ * @return array Nested array of all of the grouped opcodes.
+ */
+ public function getGroupedOpcodes($context=3)
+ {
+ $opCodes = $this->getOpCodes();
+ if(empty($opCodes)) {
+ $opCodes = array(
+ array(
+ 'equal',
+ 0,
+ 1,
+ 0,
+ 1
+ )
+ );
+ }
+
+ if($opCodes[0][0] == 'equal') {
+ $opCodes[0] = array(
+ $opCodes[0][0],
+ max($opCodes[0][1], $opCodes[0][2] - $context),
+ $opCodes[0][2],
+ max($opCodes[0][3], $opCodes[0][4] - $context),
+ $opCodes[0][4]
+ );
+ }
+
+ $lastItem = count($opCodes) - 1;
+ if($opCodes[$lastItem][0] == 'equal') {
+ list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem];
+ $opCodes[$lastItem] = array(
+ $tag,
+ $i1,
+ min($i2, $i1 + $context),
+ $j1,
+ min($j2, $j1 + $context)
+ );
+ }
+
+ $maxRange = $context * 2;
+ $groups = array();
+ $group = array();
+ foreach($opCodes as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'equal' && $i2 - $i1 > $maxRange) {
+ $group[] = array(
+ $tag,
+ $i1,
+ min($i2, $i1 + $context),
+ $j1,
+ min($j2, $j1 + $context)
+ );
+ $groups[] = $group;
+ $group = array();
+ $i1 = max($i1, $i2 - $context);
+ $j1 = max($j1, $j2 - $context);
+ }
+ $group[] = array(
+ $tag,
+ $i1,
+ $i2,
+ $j1,
+ $j2
+ );
+ }
+
+ if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) {
+ $groups[] = $group;
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Return a measure of the similarity between the two sequences.
+ * This will be a float value between 0 and 1.
+ *
+ * Out of all of the ratio calculation functions, this is the most
+ * expensive to call if getMatchingBlocks or getOpCodes is yet to be
+ * called. The other calculation methods (quickRatio and realquickRatio)
+ * can be used to perform quicker calculations but may be less accurate.
+ *
+ * The ratio is calculated as (2 * number of matches) / total number of
+ * elements in both sequences.
+ *
+ * @return float The calculated ratio.
+ */
+ public function Ratio()
+ {
+ $matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0);
+ return $this->calculateRatio($matches, count ($this->a) + count ($this->b));
+ }
+
+ /**
+ * Helper function to calculate the number of matches for Ratio().
+ *
+ * @param int $sum The running total for the number of matches.
+ * @param array $triple Array containing the matching block triple to add to the running total.
+ * @return int The new running total for the number of matches.
+ */
+ private function ratioReduce($sum, $triple)
+ {
+ return $sum + ($triple[count($triple) - 1]);
+ }
+
+ /**
+ * Quickly return an upper bound ratio for the similarity of the strings.
+ * This is quicker to compute than Ratio().
+ *
+ * @return float The calculated ratio.
+ */
+ private function quickRatio()
+ {
+ if($this->fullBCount === null) {
+ $this->fullBCount = array();
+ $bLength = count ($b);
+ for($i = 0; $i < $bLength; ++$i) {
+ $char = $this->b[$i];
+ $this->fullBCount[$char] = $this->arrayGetDefault($this->fullBCount, $char, 0) + 1;
+ }
+ }
+
+ $avail = array();
+ $matches = 0;
+ $aLength = count ($this->a);
+ for($i = 0; $i < $aLength; ++$i) {
+ $char = $this->a[$i];
+ if(isset($avail[$char])) {
+ $numb = $avail[$char];
+ }
+ else {
+ $numb = $this->arrayGetDefault($this->fullBCount, $char, 0);
+ }
+ $avail[$char] = $numb - 1;
+ if($numb > 0) {
+ ++$matches;
+ }
+ }
+
+ $this->calculateRatio($matches, count ($this->a) + count ($this->b));
+ }
+
+ /**
+ * Return an upper bound ratio really quickly for the similarity of the strings.
+ * This is quicker to compute than Ratio() and quickRatio().
+ *
+ * @return float The calculated ratio.
+ */
+ private function realquickRatio()
+ {
+ $aLength = count ($this->a);
+ $bLength = count ($this->b);
+
+ return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength);
+ }
+
+ /**
+ * Helper function for calculating the ratio to measure similarity for the strings.
+ * The ratio is defined as being 2 * (number of matches / total length)
+ *
+ * @param int $matches The number of matches in the two strings.
+ * @param int $length The length of the two strings.
+ * @return float The calculated ratio.
+ */
+ private function calculateRatio($matches, $length=0)
+ {
+ if($length) {
+ return 2 * ($matches / $length);
+ }
+ else {
+ return 1;
+ }
+ }
+
+ /**
+ * Helper function that provides the ability to return the value for a key
+ * in an array of it exists, or if it doesn't then return a default value.
+ * Essentially cleaner than doing a series of if(isset()) {} else {} calls.
+ *
+ * @param array $array The array to search.
+ * @param string $key The key to check that exists.
+ * @param mixed $default The value to return as the default value if the key doesn't exist.
+ * @return mixed The value from the array if the key exists or otherwise the default.
+ */
+ private function arrayGetDefault($array, $key, $default)
+ {
+ if(isset($array[$key])) {
+ return $array[$key];
+ }
+ else {
+ return $default;
+ }
+ }
+
+ /**
+ * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks
+ *
+ * @param array $a First array to compare.
+ * @param array $b Second array to compare.
+ * @return int -1, 0 or 1, as expected by the usort function.
+ */
+ private function tupleSort($a, $b)
+ {
+ $max = max(count($a), count($b));
+ for($i = 0; $i < $max; ++$i) {
+ if($a[$i] < $b[$i]) {
+ return -1;
+ }
+ else if($a[$i] > $b[$i]) {
+ return 1;
+ }
+ }
+
+ if(count($a) == $count($b)) {
+ return 0;
+ }
+ else if(count($a) < count($b)) {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/composer.json b/vendor/gipfl/zfdb/composer.json
new file mode 100644
index 0000000..742235a
--- /dev/null
+++ b/vendor/gipfl/zfdb/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "gipfl/zfdb",
+ "description": "Zend_Db from Zend Framework 1. For compatibility reasons only",
+ "type": "library",
+ "require": {
+ "php": ">=5.4"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\ZfDb\\": "src"
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Adapter.php b/vendor/gipfl/zfdb/src/Adapter/Adapter.php
new file mode 100644
index 0000000..a3cad5d
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Adapter.php
@@ -0,0 +1,1267 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Expr;
+use gipfl\ZfDb\Profiler;
+use gipfl\ZfDb\Profiler\ProfilerException;
+use gipfl\ZfDb\Select;
+use gipfl\ZfDb\Statement;
+use gipfl\ZfDb\Statement\StatementInterface;
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * @see Select
+ */
+
+/**
+ * Class for connecting to SQL databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Adapter
+{
+
+ /**
+ * User-provided configuration
+ *
+ * @var array
+ */
+ protected $_config = array();
+
+ /**
+ * Fetch mode
+ *
+ * @var integer
+ */
+ protected $_fetchMode = Db::FETCH_ASSOC;
+
+ /**
+ * Query profiler object, of type Zend_Db_Profiler
+ * or a subclass of that.
+ *
+ * @var Profiler
+ */
+ protected $_profiler;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = Statement::class;
+
+ /**
+ * Default class name for the profiler object.
+ *
+ * @var string
+ */
+ protected $_defaultProfilerClass = Profiler::class;
+
+ /**
+ * Database connection
+ *
+ * @var object|resource|null
+ */
+ protected $_connection = null;
+
+ /**
+ * Specifies the case of column names retrieved in queries
+ * Options
+ * Zend_Db::CASE_NATURAL (default)
+ * Zend_Db::CASE_LOWER
+ * Zend_Db::CASE_UPPER
+ *
+ * @var integer
+ */
+ protected $_caseFolding = Db::CASE_NATURAL;
+
+ /**
+ * Specifies whether the adapter automatically quotes identifiers.
+ * If true, most SQL generated by Zend_Db classes applies
+ * identifier quoting automatically.
+ * If false, developer must quote identifiers themselves
+ * by calling quoteIdentifier().
+ *
+ * @var bool
+ */
+ protected $_autoQuoteIdentifiers = true;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE
+ );
+
+ /** Weither or not that object can get serialized
+ *
+ * @var bool
+ */
+ protected $_allowSerialization = true;
+
+ /**
+ * Weither or not the database should be reconnected
+ * to that adapter when waking up
+ *
+ * @var bool
+ */
+ protected $_autoReconnectOnUnserialize = false;
+
+ /**
+ * Constructor.
+ *
+ * $config is an array of key/value pairs or an instance of Zend_Config
+ * containing configuration options. These options are common to most adapters:
+ *
+ * dbname => (string) The name of the database to user
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * host => (string) What host to connect to, defaults to localhost
+ *
+ * Some options are used on a case-by-case basis by adapters:
+ *
+ * port => (string) The port of the database
+ * persistent => (boolean) Whether to use a persistent connection or not, defaults to false
+ * protocol => (string) The network protocol, defaults to TCPIP
+ * caseFolding => (int) style of case-alteration used for identifiers
+ * socket => (string) The socket or named pipe that should be used
+ *
+ * @param array|Zend_Config $config An array or instance of Zend_Config having configuration data
+ * @throws AdapterException
+ */
+ public function __construct($config)
+ {
+ /*
+ * Verify that adapter parameters are in an array.
+ */
+ if (!is_array($config)) {
+ /*
+ * Convert Zend_Config argument to a plain array.
+ */
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ } else {
+ /**
+ * @see AdapterException
+ */
+ throw new AdapterException('Adapter parameters must be in an array or a Zend_Config object');
+ }
+ }
+
+ $this->_checkRequiredOptions($config);
+
+ $options = array(
+ Db::CASE_FOLDING => $this->_caseFolding,
+ Db::AUTO_QUOTE_IDENTIFIERS => $this->_autoQuoteIdentifiers,
+ Db::FETCH_MODE => $this->_fetchMode,
+ );
+ $driverOptions = array();
+
+ /*
+ * normalize the config and merge it with the defaults
+ */
+ if (array_key_exists('options', $config)) {
+ // can't use array_merge() because keys might be integers
+ foreach ((array) $config['options'] as $key => $value) {
+ $options[$key] = $value;
+ }
+ }
+ if (array_key_exists('driver_options', $config)) {
+ if (!empty($config['driver_options'])) {
+ // can't use array_merge() because keys might be integers
+ foreach ((array) $config['driver_options'] as $key => $value) {
+ $driverOptions[$key] = $value;
+ }
+ }
+ }
+
+ if (!isset($config['charset'])) {
+ $config['charset'] = null;
+ }
+
+ if (!isset($config['persistent'])) {
+ $config['persistent'] = false;
+ }
+
+ $this->_config = array_merge($this->_config, $config);
+ $this->_config['options'] = $options;
+ $this->_config['driver_options'] = $driverOptions;
+
+
+ // obtain the case setting, if there is one
+ if (array_key_exists(Db::CASE_FOLDING, $options)) {
+ $case = (int) $options[Db::CASE_FOLDING];
+ switch ($case) {
+ case Db::CASE_LOWER:
+ case Db::CASE_UPPER:
+ case Db::CASE_NATURAL:
+ $this->_caseFolding = $case;
+ break;
+ default:
+ /** @see AdapterException */
+ throw new AdapterException('Case must be one of the following constants: '
+ . 'Zend_Db::CASE_NATURAL, Zend_Db::CASE_LOWER, Zend_Db::CASE_UPPER');
+ }
+ }
+
+ if (array_key_exists(Db::FETCH_MODE, $options)) {
+ if (is_string($options[Db::FETCH_MODE])) {
+ $constant = 'Db::FETCH_' . strtoupper($options[Db::FETCH_MODE]);
+ if (defined($constant)) {
+ $options[Db::FETCH_MODE] = constant($constant);
+ }
+ }
+ $this->setFetchMode((int) $options[Db::FETCH_MODE]);
+ }
+
+ // obtain quoting property if there is one
+ if (array_key_exists(Db::AUTO_QUOTE_IDENTIFIERS, $options)) {
+ $this->_autoQuoteIdentifiers = (bool) $options[Db::AUTO_QUOTE_IDENTIFIERS];
+ }
+
+ // obtain allow serialization property if there is one
+ if (array_key_exists(Db::ALLOW_SERIALIZATION, $options)) {
+ $this->_allowSerialization = (bool) $options[Db::ALLOW_SERIALIZATION];
+ }
+
+ // obtain auto reconnect on unserialize property if there is one
+ if (array_key_exists(Db::AUTO_RECONNECT_ON_UNSERIALIZE, $options)) {
+ $this->_autoReconnectOnUnserialize = (bool) $options[Db::AUTO_RECONNECT_ON_UNSERIALIZE];
+ }
+
+ // create a profiler object
+ $profiler = false;
+ if (array_key_exists(Db::PROFILER, $this->_config)) {
+ $profiler = $this->_config[Db::PROFILER];
+ unset($this->_config[Db::PROFILER]);
+ }
+ $this->setProfiler($profiler);
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws AdapterException
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ /** @see AdapterException */
+ throw new AdapterException(
+ "Configuration array must have a key for 'dbname' that names the database instance"
+ );
+ }
+
+ if (! array_key_exists('password', $config)) {
+ /**
+ * @see AdapterException
+ */
+ throw new AdapterException("Configuration array must have a key for 'password' for login credentials");
+ }
+
+ if (! array_key_exists('username', $config)) {
+ /**
+ * @see AdapterException
+ */
+ throw new AdapterException("Configuration array must have a key for 'username' for login credentials");
+ }
+ }
+
+ /**
+ * Returns the underlying database connection object or resource.
+ * If not presently connected, this initiates the connection.
+ *
+ * @return object|resource|null
+ */
+ public function getConnection()
+ {
+ $this->_connect();
+ return $this->_connection;
+ }
+
+ /**
+ * Returns the configuration variables in this adapter.
+ *
+ * @return array
+ */
+ public function getConfig()
+ {
+ return $this->_config;
+ }
+
+ /**
+ * Set the adapter's profiler object.
+ *
+ * The argument may be a boolean, an associative array, an instance of
+ * Zend_Db_Profiler, or an instance of Zend_Config.
+ *
+ * A boolean argument sets the profiler to enabled if true, or disabled if
+ * false. The profiler class is the adapter's default profiler class,
+ * Zend_Db_Profiler.
+ *
+ * An instance of Zend_Db_Profiler sets the adapter's instance to that
+ * object. The profiler is enabled and disabled separately.
+ *
+ * An associative array argument may contain any of the keys 'enabled',
+ * 'class', and 'instance'. The 'enabled' and 'instance' keys correspond to the
+ * boolean and object types documented above. The 'class' key is used to name a
+ * class to use for a custom profiler. The class must be Zend_Db_Profiler or a
+ * subclass. The class is instantiated with no constructor arguments. The 'class'
+ * option is ignored when the 'instance' option is supplied.
+ *
+ * An object of type Zend_Config may contain the properties 'enabled', 'class', and
+ * 'instance', just as if an associative array had been passed instead.
+ *
+ * @param Profiler|Zend_Config|array|boolean $profiler
+ * @return Adapter Provides a fluent interface
+ * @throws ProfilerException if the object instance or class specified
+ * is not Zend_Db_Profiler or an extension of that class.
+ */
+ public function setProfiler($profiler)
+ {
+ $enabled = null;
+ $profilerClass = $this->_defaultProfilerClass;
+ $profilerInstance = null;
+
+ if ($profilerIsObject = is_object($profiler)) {
+ if ($profiler instanceof Profiler) {
+ $profilerInstance = $profiler;
+ } elseif ($profiler instanceof Zend_Config) {
+ $profiler = $profiler->toArray();
+ } else {
+ throw new ProfilerException('Profiler argument must be an instance of either Profiler'
+ . ' or Zend_Config when provided as an object');
+ }
+ }
+
+ if (is_array($profiler)) {
+ if (isset($profiler['enabled'])) {
+ $enabled = (bool) $profiler['enabled'];
+ }
+ if (isset($profiler['class'])) {
+ $profilerClass = $profiler['class'];
+ }
+ if (isset($profiler['instance'])) {
+ $profilerInstance = $profiler['instance'];
+ }
+ } elseif (!$profilerIsObject) {
+ $enabled = (bool) $profiler;
+ }
+
+ if ($profilerInstance === null) {
+ $profilerInstance = new $profilerClass();
+ }
+
+ if (!$profilerInstance instanceof Profiler) {
+ throw new ProfilerException('Class ' . get_class($profilerInstance) . ' does not extend '
+ . 'Zend_Db_Profiler');
+ }
+
+ if (null !== $enabled) {
+ $profilerInstance->setEnabled($enabled);
+ }
+
+ $this->_profiler = $profilerInstance;
+
+ return $this;
+ }
+
+
+ /**
+ * Returns the profiler for this adapter.
+ *
+ * @return Profiler
+ */
+ public function getProfiler()
+ {
+ return $this->_profiler;
+ }
+
+ /**
+ * Get the default statement class.
+ *
+ * @return string
+ */
+ public function getStatementClass()
+ {
+ return $this->_defaultStmtClass;
+ }
+
+ /**
+ * Set the default statement class.
+ *
+ * @return Adapter Fluent interface
+ */
+ public function setStatementClass($class)
+ {
+ $this->_defaultStmtClass = $class;
+ return $this;
+ }
+
+ /**
+ * Prepares and executes an SQL statement with bound data.
+ *
+ * @param mixed $sql The SQL statement with placeholders.
+ * May be a string or Zend_Db_Select.
+ * @param mixed $bind An array of data to bind to the placeholders.
+ * @return StatementInterface
+ */
+ public function query($sql, $bind = array())
+ {
+ // connect to the database if needed
+ $this->_connect();
+
+ // is the $sql a Zend_Db_Select object?
+ if ($sql instanceof Select) {
+ if (empty($bind)) {
+ $bind = $sql->getBind();
+ }
+
+ $sql = $sql->assemble();
+ }
+
+ // make sure $bind to an array;
+ // don't use (array) typecasting because
+ // because $bind may be a Zend_Db_Expr object
+ if (!is_array($bind)) {
+ $bind = array($bind);
+ }
+
+ // prepare and execute the statement with profiling
+ $stmt = $this->prepare($sql);
+ $stmt->execute($bind);
+
+ // return the results embedded in the prepared statement object
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return Adapter
+ */
+ public function beginTransaction()
+ {
+ $this->_connect();
+ $q = $this->_profiler->queryStart('begin', Profiler::TRANSACTION);
+ $this->_beginTransaction();
+ $this->_profiler->queryEnd($q);
+ return $this;
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return Adapter
+ */
+ public function commit()
+ {
+ $this->_connect();
+ $q = $this->_profiler->queryStart('commit', Profiler::TRANSACTION);
+ $this->_commit();
+ $this->_profiler->queryEnd($q);
+ return $this;
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return Adapter
+ */
+ public function rollBack()
+ {
+ $this->_connect();
+ $q = $this->_profiler->queryStart('rollback', Profiler::TRANSACTION);
+ $this->_rollBack();
+ $this->_profiler->queryEnd($q);
+ return $this;
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ * @throws AdapterException|\gipfl\ZfDb\Statement\Exception\StatementException
+ */
+ public function insert($table, array $bind)
+ {
+ // extract and quote col names from the array keys
+ $cols = array();
+ $vals = array();
+ $i = 0;
+ foreach ($bind as $col => $val) {
+ $cols[] = $this->quoteIdentifier($col, true);
+ if ($val instanceof Expr) {
+ $vals[] = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ if ($this->supportsParameters('positional')) {
+ $vals[] = '?';
+ } else {
+ if ($this->supportsParameters('named')) {
+ unset($bind[$col]);
+ $bind[':col'.$i] = $val;
+ $vals[] = ':col'.$i;
+ $i++;
+ } else {
+ throw new AdapterException(get_class($this) ." doesn't support positional or named binding");
+ }
+ }
+ }
+ }
+
+ // build the statement
+ $sql = "INSERT INTO "
+ . $this->quoteIdentifier($table, true)
+ . ' (' . implode(', ', $cols) . ') '
+ . 'VALUES (' . implode(', ', $vals) . ')';
+
+ // execute the statement and return the number of affected rows
+ if ($this->supportsParameters('positional')) {
+ $bind = array_values($bind);
+ }
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->rowCount();
+ return $result;
+ }
+
+ /**
+ * Updates table rows with specified data based on a WHERE clause.
+ *
+ * @param mixed $table The table to update.
+ * @param array $bind Column-value pairs.
+ * @param mixed $where UPDATE WHERE clause(s).
+ * @return int The number of affected rows.
+ * @throws AdapterException
+ */
+ public function update($table, array $bind, $where = '')
+ {
+ /**
+ * Build "col = ?" pairs for the statement,
+ * except for Zend_Db_Expr which is treated literally.
+ */
+ $set = array();
+ $i = 0;
+ foreach ($bind as $col => $val) {
+ if ($val instanceof Expr) {
+ $val = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ if ($this->supportsParameters('positional')) {
+ $val = '?';
+ } else {
+ if ($this->supportsParameters('named')) {
+ unset($bind[$col]);
+ $bind[':col'.$i] = $val;
+ $val = ':col'.$i;
+ $i++;
+ } else {
+ /** @see AdapterException */
+ throw new AdapterException(get_class($this) ." doesn't support positional or named binding");
+ }
+ }
+ }
+ $set[] = $this->quoteIdentifier($col, true) . ' = ' . $val;
+ }
+
+ $where = $this->_whereExpr($where);
+
+ /**
+ * Build the UPDATE statement
+ */
+ $sql = "UPDATE "
+ . $this->quoteIdentifier($table, true)
+ . ' SET ' . implode(', ', $set)
+ . (($where) ? " WHERE $where" : '');
+
+ /**
+ * Execute the statement and return the number of affected rows
+ */
+ if ($this->supportsParameters('positional')) {
+ $stmt = $this->query($sql, array_values($bind));
+ } else {
+ $stmt = $this->query($sql, $bind);
+ }
+ $result = $stmt->rowCount();
+ return $result;
+ }
+
+ /**
+ * Deletes table rows based on a WHERE clause.
+ *
+ * @param mixed $table The table to update.
+ * @param mixed $where DELETE WHERE clause(s).
+ * @return int The number of affected rows.
+ */
+ public function delete($table, $where = '')
+ {
+ $where = $this->_whereExpr($where);
+
+ /**
+ * Build the DELETE statement
+ */
+ $sql = "DELETE FROM "
+ . $this->quoteIdentifier($table, true)
+ . (($where) ? " WHERE $where" : '');
+
+ /**
+ * Execute the statement and return the number of affected rows
+ */
+ $stmt = $this->query($sql);
+ $result = $stmt->rowCount();
+ return $result;
+ }
+
+ /**
+ * Convert an array, string, or Zend_Db_Expr object
+ * into a string to put in a WHERE clause.
+ *
+ * @param mixed $where
+ * @return string
+ */
+ protected function _whereExpr($where)
+ {
+ if (empty($where)) {
+ return $where;
+ }
+ if (!is_array($where)) {
+ $where = array($where);
+ }
+ foreach ($where as $cond => &$term) {
+ // is $cond an int? (i.e. Not a condition)
+ if (is_int($cond)) {
+ // $term is the full condition
+ if ($term instanceof Expr) {
+ $term = $term->__toString();
+ }
+ } else {
+ // $cond is the condition with placeholder,
+ // and $term is quoted into the condition
+ $term = $this->quoteInto($cond, $term);
+ }
+ $term = '(' . $term . ')';
+ }
+
+ $where = implode(' AND ', $where);
+ return $where;
+ }
+
+ /**
+ * Creates and returns a new Zend_Db_Select object for this adapter.
+ *
+ * @return Select
+ */
+ public function select()
+ {
+ return new Select($this);
+ }
+
+ /**
+ * Get the fetch mode.
+ *
+ * @return int
+ */
+ public function getFetchMode()
+ {
+ return $this->_fetchMode;
+ }
+
+ /**
+ * Fetches all SQL result rows as a sequential array.
+ * Uses the current fetchMode for the adapter.
+ *
+ * @param string|Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @param mixed $fetchMode Override current fetch mode.
+ * @return array
+ */
+ public function fetchAll($sql, $bind = array(), $fetchMode = null)
+ {
+ if ($fetchMode === null) {
+ $fetchMode = $this->_fetchMode;
+ }
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetchAll($fetchMode);
+ return $result;
+ }
+
+ /**
+ * Fetches the first row of the SQL result.
+ * Uses the current fetchMode for the adapter.
+ *
+ * @param string|Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @param mixed $fetchMode Override current fetch mode.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ */
+ public function fetchRow($sql, $bind = array(), $fetchMode = null)
+ {
+ if ($fetchMode === null) {
+ $fetchMode = $this->_fetchMode;
+ }
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetch($fetchMode);
+ return $result;
+ }
+
+ /**
+ * Fetches all SQL result rows as an associative array.
+ *
+ * The first column is the key, the entire row array is the
+ * value. You should construct the query to be sure that
+ * the first column contains unique values, or else
+ * rows with duplicate values in the first column will
+ * overwrite previous data.
+ *
+ * @param string|Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return array
+ */
+ public function fetchAssoc($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $data = array();
+ while ($row = $stmt->fetch(Db::FETCH_ASSOC)) {
+ $tmp = array_values(array_slice($row, 0, 1));
+ $data[$tmp[0]] = $row;
+ }
+ return $data;
+ }
+
+ /**
+ * Fetches the first column of all SQL result rows as an array.
+ *
+ * @param string|Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return array
+ */
+ public function fetchCol($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetchAll(Db::FETCH_COLUMN, 0);
+ return $result;
+ }
+
+ /**
+ * Fetches all SQL result rows as an array of key-value pairs.
+ *
+ * The first column is the key, the second column is the
+ * value.
+ *
+ * @param string|Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return array
+ */
+ public function fetchPairs($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $data = array();
+ while ($row = $stmt->fetch(Db::FETCH_NUM)) {
+ $data[$row[0]] = $row[1];
+ }
+ return $data;
+ }
+
+ /**
+ * Fetches the first column of the first row of the SQL result.
+ *
+ * @param string|Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return string
+ */
+ public function fetchOne($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetchColumn(0);
+ return $result;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value)) {
+ return $value;
+ } elseif (is_float($value)) {
+ return sprintf('%F', $value);
+ }
+ return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'";
+ }
+
+ /**
+ * Safely quotes a value for an SQL statement.
+ *
+ * If an array is passed as the value, the array values are quoted
+ * and then returned as a comma-separated string.
+ *
+ * @param mixed $value The value to quote.
+ * @param mixed $type OPTIONAL the SQL datatype name, or constant, or null.
+ * @return mixed An SQL-safe quoted value (or string of separated values).
+ */
+ public function quote($value, $type = null)
+ {
+ $this->_connect();
+
+ if ($value instanceof Select) {
+ return '(' . $value->assemble() . ')';
+ }
+
+ if ($value instanceof Expr) {
+ return $value->__toString();
+ }
+
+ if (is_array($value)) {
+ foreach ($value as &$val) {
+ $val = $this->quote($val, $type);
+ }
+ return implode(', ', $value);
+ }
+
+ if ($type !== null && array_key_exists($type = strtoupper($type), $this->_numericDataTypes)) {
+ $quotedValue = '0';
+ switch ($this->_numericDataTypes[$type]) {
+ case Db::INT_TYPE: // 32-bit integer
+ $quotedValue = (string) intval($value);
+ break;
+ case Db::BIGINT_TYPE: // 64-bit integer
+ // ANSI SQL-style hex literals (e.g. x'[\dA-F]+')
+ // are not supported here, because these are string
+ // literals, not numeric literals.
+ if (preg_match(
+ '/^(
+ [+-]? # optional sign
+ (?:
+ 0[Xx][\da-fA-F]+ # ODBC-style hexadecimal
+ |\d+ # decimal or octal, or MySQL ZEROFILL decimal
+ (?:[eE][+-]?\d+)? # optional exponent on decimals or octals
+ )
+ )/x',
+ (string) $value,
+ $matches
+ )) {
+ $quotedValue = $matches[1];
+ }
+ break;
+ case Db::FLOAT_TYPE: // float or decimal
+ $quotedValue = sprintf('%F', $value);
+ }
+ return $quotedValue;
+ }
+
+ return $this->_quote($value);
+ }
+
+ /**
+ * Quotes a value and places into a piece of text at a placeholder.
+ *
+ * The placeholder is a question-mark; all placeholders will be replaced
+ * with the quoted value. For example:
+ *
+ * <code>
+ * $text = "WHERE date < ?";
+ * $date = "2005-01-02";
+ * $safe = $sql->quoteInto($text, $date);
+ * // $safe = "WHERE date < '2005-01-02'"
+ * </code>
+ *
+ * @param string $text The text with a placeholder.
+ * @param mixed $value The value to quote.
+ * @param string $type OPTIONAL SQL datatype
+ * @param integer $count OPTIONAL count of placeholders to replace
+ * @return string An SQL-safe quoted value placed into the original text.
+ */
+ public function quoteInto($text, $value, $type = null, $count = null)
+ {
+ if ($count === null) {
+ return str_replace('?', $this->quote($value, $type), $text);
+ } else {
+ return implode($this->quote($value, $type), explode('?', $text, $count + 1));
+ }
+ }
+
+ /**
+ * Quotes an identifier.
+ *
+ * Accepts a string representing a qualified indentifier. For Example:
+ * <code>
+ * $adapter->quoteIdentifier('myschema.mytable')
+ * </code>
+ * Returns: "myschema"."mytable"
+ *
+ * Or, an array of one or more identifiers that may form a qualified identifier:
+ * <code>
+ * $adapter->quoteIdentifier(array('myschema','my.table'))
+ * </code>
+ * Returns: "myschema"."my.table"
+ *
+ * The actual quote character surrounding the identifiers may vary depending on
+ * the adapter.
+ *
+ * @param string|array|Expr $ident The identifier.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier.
+ */
+ public function quoteIdentifier($ident, $auto = false)
+ {
+ return $this->_quoteIdentifierAs($ident, null, $auto);
+ }
+
+ /**
+ * Quote a column identifier and alias.
+ *
+ * @param string|array|Expr $ident The identifier or expression.
+ * @param string $alias An alias for the column.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteColumnAs($ident, $alias, $auto = false)
+ {
+ return $this->_quoteIdentifierAs($ident, $alias, $auto);
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ return $this->_quoteIdentifierAs($ident, $alias, $auto);
+ }
+
+ /**
+ * Quote an identifier and an optional alias.
+ *
+ * @param string|array|Expr $ident The identifier or expression.
+ * @param string $alias An optional alias.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @param string $as The string to add between the identifier/expression and the alias.
+ * @return string The quoted identifier and alias.
+ */
+ protected function _quoteIdentifierAs($ident, $alias = null, $auto = false, $as = ' AS ')
+ {
+ if ($ident instanceof Expr) {
+ $quoted = $ident->__toString();
+ } elseif ($ident instanceof Select) {
+ $quoted = '(' . $ident->assemble() . ')';
+ } else {
+ if (is_string($ident)) {
+ $ident = explode('.', $ident);
+ }
+ if (is_array($ident)) {
+ $segments = array();
+ foreach ($ident as $segment) {
+ if ($segment instanceof Expr) {
+ $segments[] = $segment->__toString();
+ } else {
+ $segments[] = $this->_quoteIdentifier($segment, $auto);
+ }
+ }
+ if ($alias !== null && end($ident) == $alias) {
+ $alias = null;
+ }
+ $quoted = implode('.', $segments);
+ } else {
+ $quoted = $this->_quoteIdentifier($ident, $auto);
+ }
+ }
+ if ($alias !== null) {
+ $quoted .= $as . $this->_quoteIdentifier($alias, $auto);
+ }
+ return $quoted;
+ }
+
+ /**
+ * Quote an identifier.
+ *
+ * @param string $value The identifier or expression.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ protected function _quoteIdentifier($value, $auto = false)
+ {
+ if ($auto === false || $this->_autoQuoteIdentifiers === true) {
+ $q = $this->getQuoteIdentifierSymbol();
+ return ($q . str_replace("$q", "$q$q", $value) . $q);
+ }
+ return $value;
+ }
+
+ /**
+ * Returns the symbol the adapter uses for delimited identifiers.
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ return null;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ return null;
+ }
+
+ /**
+ * Helper method to change the case of the strings used
+ * when returning result sets in FETCH_ASSOC and FETCH_BOTH
+ * modes.
+ *
+ * This is not intended to be used by application code,
+ * but the method must be public so the Statement class
+ * can invoke it.
+ *
+ * @param string $key
+ * @return string
+ */
+ public function foldCase($key)
+ {
+ switch ($this->_caseFolding) {
+ case Db::CASE_LOWER:
+ $value = strtolower((string) $key);
+ break;
+ case Db::CASE_UPPER:
+ $value = strtoupper((string) $key);
+ break;
+ case Db::CASE_NATURAL:
+ default:
+ $value = (string) $key;
+ }
+ return $value;
+ }
+
+ /**
+ * called when object is getting serialized
+ * This disconnects the DB object that cant be serialized
+ *
+ * @return array
+ * @throws AdapterException
+ */
+ public function __sleep()
+ {
+ if ($this->_allowSerialization == false) {
+ /** @see AdapterException */
+ throw new AdapterException(
+ get_class($this) . ' is not allowed to be serialized'
+ );
+ }
+ $this->_connection = null;
+
+ return array_keys(
+ array_diff_key(get_object_vars($this), array('_connection' => null))
+ );
+ }
+
+ /**
+ * called when object is getting unserialized
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ if ($this->_autoReconnectOnUnserialize == true) {
+ $this->getConnection();
+ }
+ }
+
+ /**
+ * Abstract Methods
+ */
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ abstract public function listTables();
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ abstract public function describeTable($tableName, $schemaName = null);
+
+ /**
+ * Creates a connection to the database.
+ *
+ * @return void
+ */
+ abstract protected function _connect();
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ abstract public function isConnected();
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ abstract public function closeConnection();
+
+ /**
+ * Prepare a statement and return a PDOStatement-like object.
+ *
+ * @param string|Select $sql SQL query
+ * @return Statement|\PDOStatement
+ */
+ abstract public function prepare($sql);
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ abstract public function lastInsertId($tableName = null, $primaryKey = null);
+
+ /**
+ * Begin a transaction.
+ */
+ abstract protected function _beginTransaction();
+
+ /**
+ * Commit a transaction.
+ */
+ abstract protected function _commit();
+
+ /**
+ * Roll-back a transaction.
+ */
+ abstract protected function _rollBack();
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode
+ * @return void
+ * @throws AdapterException
+ */
+ abstract public function setFetchMode($mode);
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param mixed $sql
+ * @param integer $count
+ * @param integer $offset
+ * @return string
+ */
+ abstract public function limit($sql, $count, $offset = 0);
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ abstract public function supportsParameters($type);
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ abstract public function getServerVersion();
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Db2.php b/vendor/gipfl/zfdb/src/Adapter/Db2.php
new file mode 100644
index 0000000..55dce72
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Db2.php
@@ -0,0 +1,792 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ *
+ */
+namespace gipfl\ZfDb\Adapter;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterExceptionDb2;
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Statement\Db2Statement;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Db2 extends Adapter
+{
+ /**
+ * User-provided configuration.
+ *
+ * Basic keys are:
+ *
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * host => (string) What host to connect to (default 127.0.0.1)
+ * dbname => (string) The name of the database to user
+ * protocol => (string) Protocol to use, defaults to "TCPIP"
+ * port => (integer) Port number to use for TCP/IP if protocol is "TCPIP"
+ * persistent => (boolean) Set TRUE to use a persistent connection (db2_pconnect)
+ * os => (string) This should be set to 'i5' if the db is on an os400/i5
+ * schema => (string) The default schema the connection should use
+ *
+ * @var array
+ */
+ protected $_config = array(
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ 'host' => 'localhost',
+ 'port' => '50000',
+ 'protocol' => 'TCPIP',
+ 'persistent' => false,
+ 'os' => null,
+ 'schema' => null
+ );
+
+ /**
+ * Execution mode
+ *
+ * @var int execution flag (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF)
+ */
+ protected $_execute_mode = DB2_AUTOCOMMIT_ON;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = Db2Statement::class;
+ protected $_isI5 = false;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INTEGER' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'NUMERIC' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('ibm_db2')) {
+ throw new AdapterExceptionDb2(
+ 'The IBM DB2 extension is required for this adapter but the extension is not loaded'
+ );
+ }
+
+ $this->_determineI5();
+ if ($this->_config['persistent']) {
+ // use persistent connection
+ $conn_func_name = 'db2_pconnect';
+ } else {
+ // use "normal" connection
+ $conn_func_name = 'db2_connect';
+ }
+
+ if (!isset($this->_config['driver_options']['autocommit'])) {
+ // set execution mode
+ $this->_config['driver_options']['autocommit'] = &$this->_execute_mode;
+ }
+
+ if (isset($this->_config['options'][Db::CASE_FOLDING])) {
+ $caseAttrMap = array(
+ Db::CASE_NATURAL => DB2_CASE_NATURAL,
+ Db::CASE_UPPER => DB2_CASE_UPPER,
+ Db::CASE_LOWER => DB2_CASE_LOWER
+ );
+ $this->_config['driver_options']['DB2_ATTR_CASE']
+ = $caseAttrMap[$this->_config['options'][Db::CASE_FOLDING]];
+ }
+
+ if ($this->_isI5 && isset($this->_config['driver_options']['i5_naming'])) {
+ if ($this->_config['driver_options']['i5_naming']) {
+ $this->_config['driver_options']['i5_naming'] = DB2_I5_NAMING_ON;
+ } else {
+ $this->_config['driver_options']['i5_naming'] = DB2_I5_NAMING_OFF;
+ }
+ }
+
+ if ($this->_config['host'] !== 'localhost' && !$this->_isI5) {
+ // if the host isn't localhost, use extended connection params
+ $dbname = 'DRIVER={IBM DB2 ODBC DRIVER}' .
+ ';DATABASE=' . $this->_config['dbname'] .
+ ';HOSTNAME=' . $this->_config['host'] .
+ ';PORT=' . $this->_config['port'] .
+ ';PROTOCOL=' . $this->_config['protocol'] .
+ ';UID=' . $this->_config['username'] .
+ ';PWD=' . $this->_config['password'] .';';
+ $this->_connection = $conn_func_name(
+ $dbname,
+ null,
+ null,
+ $this->_config['driver_options']
+ );
+ } else {
+ // host is localhost, so use standard connection params
+ $this->_connection = $conn_func_name(
+ $this->_config['dbname'],
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+ }
+
+ // check the connection
+ if (!$this->_connection) {
+ throw new AdapterExceptionDb2(db2_conn_errormsg(), db2_conn_error());
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) (is_resource($this->_connection)
+ && get_resource_type($this->_connection) == 'DB2 Connection'));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ db2_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Db2Statement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Gets the execution mode
+ *
+ * @return int the execution mode (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF)
+ */
+ public function _getExecuteMode()
+ {
+ return $this->_execute_mode;
+ }
+
+ /**
+ * @param integer $mode
+ * @return void
+ */
+ public function _setExecuteMode($mode)
+ {
+ switch ($mode) {
+ case DB2_AUTOCOMMIT_OFF:
+ case DB2_AUTOCOMMIT_ON:
+ $this->_execute_mode = $mode;
+ db2_autocommit($this->_connection, $mode);
+ break;
+ default:
+ /**
+ * @see AdapterExceptionDb2
+ */
+ throw new AdapterExceptionDb2("execution mode not supported");
+ break;
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ /**
+ * Use db2_escape_string() if it is present in the IBM DB2 extension.
+ * But some supported versions of PHP do not include this function,
+ * so fall back to default quoting in the parent class.
+ */
+ if (function_exists('db2_escape_string')) {
+ return "'" . db2_escape_string($value) . "'";
+ }
+ return parent::_quote($value);
+ }
+
+ /**
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ $this->_connect();
+ $info = db2_server_info($this->_connection);
+ if ($info) {
+ $identQuote = $info->IDENTIFIER_QUOTE_CHAR;
+ } else {
+ // db2_server_info() does not return result on some i5 OS version
+ if ($this->_isI5) {
+ $identQuote ="'";
+ }
+ }
+ return $identQuote;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ * @param string $schema OPTIONAL
+ * @return array
+ */
+ public function listTables($schema = null)
+ {
+ $this->_connect();
+
+ if ($schema === null && $this->_config['schema'] != null) {
+ $schema = $this->_config['schema'];
+ }
+
+ $tables = array();
+
+ if (!$this->_isI5) {
+ if ($schema) {
+ $stmt = db2_tables($this->_connection, null, $schema);
+ } else {
+ $stmt = db2_tables($this->_connection);
+ }
+ while ($row = db2_fetch_assoc($stmt)) {
+ $tables[] = $row['TABLE_NAME'];
+ }
+ } else {
+ $tables = $this->_i5listTables($schema);
+ }
+
+ return $tables;
+ }
+
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * DB2 not supports UNSIGNED integer.
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // Ensure the connection is made so that _isI5 is set
+ $this->_connect();
+
+ if ($schemaName === null && $this->_config['schema'] != null) {
+ $schemaName = $this->_config['schema'];
+ }
+
+ if (!$this->_isI5) {
+ $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE "
+ . $this->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName);
+
+ if ($schemaName) {
+ $sql .= $this->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName);
+ }
+
+ $sql .= " ORDER BY c.colno";
+ } else {
+ // DB2 On I5 specific query
+ $sql = "SELECT DISTINCT C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME, C.ORDINAL_POSITION,
+ C.DATA_TYPE, C.COLUMN_DEFAULT, C.NULLS ,C.LENGTH, C.SCALE, LEFT(C.IDENTITY,1),
+ LEFT(tc.TYPE, 1) AS tabconsttype, k.COLSEQ
+ FROM QSYS2.SYSCOLUMNS C
+ LEFT JOIN (QSYS2.syskeycst k JOIN QSYS2.SYSCST tc
+ ON (k.TABLE_SCHEMA = tc.TABLE_SCHEMA
+ AND k.TABLE_NAME = tc.TABLE_NAME
+ AND LEFT(tc.type,1) = 'P'))
+ ON (C.TABLE_SCHEMA = k.TABLE_SCHEMA
+ AND C.TABLE_NAME = k.TABLE_NAME
+ AND C.COLUMN_NAME = k.COLUMN_NAME)
+ WHERE "
+ . $this->quoteInto('UPPER(C.TABLE_NAME) = UPPER(?)', $tableName);
+
+ if ($schemaName) {
+ $sql .= $this->quoteInto(' AND UPPER(C.TABLE_SCHEMA) = UPPER(?)', $schemaName);
+ }
+
+ $sql .= " ORDER BY C.ORDINAL_POSITION FOR FETCH ONLY";
+ }
+
+ $desc = array();
+ $stmt = $this->query($sql);
+
+ /**
+ * To avoid case issues, fetch using FETCH_NUM
+ */
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $nulls = 6;
+ $length = 7;
+ $scale = 8;
+ $identityCol = 9;
+ $tabconstType = 10;
+ $colseq = 11;
+
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$tabconstType] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$colseq];
+ }
+ /**
+ * In IBM DB2, an column can be IDENTITY
+ * even if it is not part of the PRIMARY KEY.
+ */
+ if ($row[$identityCol] == 'Y') {
+ $identity = true;
+ }
+
+ // only colname needs to be case adjusted
+ $desc[$this->foldCase($row[$colname])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => (!$this->_isI5) ? $row[$colno]+1 : $row[$colno],
+ 'DATA_TYPE' => $row[$typename],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$nulls] == 'Y'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+
+ if (!$this->_isI5) {
+ $quotedSequenceName = $this->quoteIdentifier($sequenceName, true);
+ $sql = 'SELECT PREVVAL FOR ' . $quotedSequenceName . ' AS VAL FROM SYSIBM.SYSDUMMY1';
+ } else {
+ $quotedSequenceName = $sequenceName;
+ $sql = 'SELECT PREVVAL FOR ' . $this->quoteIdentifier($sequenceName, true) . ' AS VAL FROM QSYS2.QSQPTABL';
+ }
+
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT NEXTVAL FOR '.$this->quoteIdentifier($sequenceName, true).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * The IDENTITY_VAL_LOCAL() function gives the last generated identity value
+ * in the current process, even if it was for a GENERATED column.
+ *
+ * @param string $tableName OPTIONAL
+ * @param string $primaryKey OPTIONAL
+ * @param string $idType OPTIONAL used for i5 platform to define sequence/idenity unique value
+ * @return string
+ */
+
+ public function lastInsertId($tableName = null, $primaryKey = null, $idType = null)
+ {
+ $this->_connect();
+
+ if ($this->_isI5) {
+ return (string) $this->_i5LastInsertId($tableName, $idType);
+ }
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_OFF);
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ protected function _commit()
+ {
+ if (!db2_commit($this->_connection)) {
+ /**
+ * @see AdapterExceptionDb2
+ */
+ throw new AdapterExceptionDb2(
+ db2_conn_errormsg($this->_connection),
+ db2_conn_error($this->_connection)
+ );
+ }
+
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_ON);
+ }
+
+ /**
+ * Rollback a transaction.
+ *
+ * @return void
+ */
+ protected function _rollBack()
+ {
+ if (!db2_rollback($this->_connection)) {
+ /**
+ * @see AdapterExceptionDb2
+ */
+ throw new AdapterExceptionDb2(
+ db2_conn_errormsg($this->_connection),
+ db2_conn_error($this->_connection)
+ );
+ }
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_ON);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode
+ * @return void
+ * @throws AdapterExceptionDb2
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Db::FETCH_NUM: // seq array
+ case Db::FETCH_ASSOC: // assoc array
+ case Db::FETCH_BOTH: // seq+assoc array
+ case Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Db::FETCH_BOUND: // bound to PHP variable
+ throw new AdapterExceptionDb2('FETCH_BOUND is not supported yet');
+ default:
+ throw new AdapterExceptionDb2("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterExceptionDb2("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterExceptionDb2("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0) {
+ $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY";
+ return $limit_sql;
+ }
+
+ /**
+ * DB2 does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.*
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ return $limit_sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ if ($type == 'positional') {
+ return true;
+ }
+
+ // if its 'named' or anything else
+ return false;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $server_info = db2_server_info($this->_connection);
+ if ($server_info !== false) {
+ $version = $server_info->DBMS_VER;
+ if ($this->_isI5) {
+ $version = (int) substr($version, 0, 2)
+ . '.' . (int) substr($version, 2, 2)
+ . '.' . (int) substr($version, 4);
+ }
+ return $version;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return whether or not this is running on i5
+ *
+ * @return bool
+ */
+ public function isI5()
+ {
+ if ($this->_isI5 === null) {
+ $this->_determineI5();
+ }
+
+ return (bool) $this->_isI5;
+ }
+
+ /**
+ * Check the connection parameters according to verify
+ * type of used OS
+ *
+ * @return void
+ */
+ protected function _determineI5()
+ {
+ // first us the compiled flag.
+ $this->_isI5 = (php_uname('s') == 'OS400') ? true : false;
+
+ // if this is set, then us it
+ if (isset($this->_config['os'])) {
+ if (strtolower($this->_config['os']) === 'i5') {
+ $this->_isI5 = true;
+ } else {
+ // any other value passed in, its null
+ $this->_isI5 = false;
+ }
+ }
+ }
+
+ /**
+ * Db2 On I5 specific method
+ *
+ * Returns a list of the tables in the database .
+ * Used only for DB2/400.
+ *
+ * @return array
+ */
+ protected function _i5listTables($schema = null)
+ {
+ //list of i5 libraries.
+ $tables = array();
+ if ($schema) {
+ $tablesStatement = db2_tables($this->_connection, null, $schema);
+ while ($rowTables = db2_fetch_assoc($tablesStatement)) {
+ if ($rowTables['TABLE_NAME'] !== null) {
+ $tables[] = $rowTables['TABLE_NAME'];
+ }
+ }
+ } else {
+ $schemaStatement = db2_tables($this->_connection);
+ while ($schema = db2_fetch_assoc($schemaStatement)) {
+ if ($schema['TABLE_SCHEM'] !== null) {
+ // list of the tables which belongs to the selected library
+ $tablesStatement = db2_tables($this->_connection, null, $schema['TABLE_SCHEM']);
+ if (is_resource($tablesStatement)) {
+ while ($rowTables = db2_fetch_assoc($tablesStatement)) {
+ if ($rowTables['TABLE_NAME'] !== null) {
+ $tables[] = $rowTables['TABLE_NAME'];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $tables;
+ }
+
+ protected function _i5LastInsertId($objectName = null, $idType = null)
+ {
+
+ if ($objectName === null) {
+ $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM QSYS2.QSQPTABL';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ if (strtoupper($idType) === 'S') {
+ //check i5_lib option
+ $sequenceName = $objectName;
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ //returns last identity value for the specified table
+ //if (strtoupper($idType) === 'I') {
+ $tableName = $objectName;
+ return $this->fetchOne('SELECT IDENTITY_VAL_LOCAL() from ' . $this->quoteIdentifier($tableName));
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterException.php b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterException.php
new file mode 100644
index 0000000..67e000d
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterException.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Exception;
+
+use gipfl\ZfDb\Exception\DbException;
+use Exception;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class AdapterException extends DbException
+{
+ protected $_chainedException = null;
+
+ public function __construct($message = '', $code = 0, Exception $e = null)
+ {
+ if ($e && (0 === $code)) {
+ $code = $e->getCode();
+ $code = ctype_digit($code) ? (int) $code : 0;
+ }
+ parent::__construct($message, $code, $e);
+ }
+
+ public function hasChainedException()
+ {
+ return ($this->getPrevious() !== null);
+ }
+
+ public function getChainedException()
+ {
+ return $this->getPrevious();
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionDb2.php b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionDb2.php
new file mode 100644
index 0000000..c43b36f
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionDb2.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Exception;
+
+use Exception;
+
+/**
+ * Zend_Db_Adapter_Exception
+ */
+
+/**
+ * Zend_Db_Adapter_Db2_Exception
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class AdapterExceptionDb2 extends AdapterException
+{
+ protected $code = '00000';
+ protected $message = 'unknown exception';
+
+ public function __construct($message = 'unknown exception', $code = '00000', Exception $e = null)
+ {
+ parent::__construct($message, $code, $e);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionMysqli.php b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionMysqli.php
new file mode 100644
index 0000000..99029b4
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionMysqli.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ *
+ */
+namespace gipfl\ZfDb\Adapter\Exception;
+
+/**
+ * Zend_Db_Adapter_Mysqli_Exception
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class AdapterExceptionMysqli extends AdapterException
+{
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionOracle.php b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionOracle.php
new file mode 100644
index 0000000..2d8f45d
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionOracle.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Exception;
+
+/**
+ * Zend_Db_Adapter_Oracle_Exception
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class AdapterExceptionOracle extends AdapterException
+{
+ protected $message = 'Unknown exception';
+ protected $code = 0;
+
+ public function __construct($error = null, $code = 0)
+ {
+ if (is_array($error)) {
+ if (!isset($error['offset'])) {
+ $this->message = $error['code'] .' '. $error['message'];
+ } else {
+ $this->message = $error['code'] .' '. $error['message']." "
+ . substr($error['sqltext'], 0, $error['offset'])
+ . "*"
+ . substr($error['sqltext'], $error['offset']);
+ }
+ $this->code = $error['code'];
+ } elseif (is_string($error)) {
+ $this->message = $error;
+ }
+ if (!$this->code && $code) {
+ $this->code = $code;
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionSqlsrv.php b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionSqlsrv.php
new file mode 100644
index 0000000..dde821a
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Exception/AdapterExceptionSqlsrv.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Exception;
+
+use Exception;
+
+/**
+ * Zend_Db_Adapter_Sqlsrv_Exception
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class AdapterExceptionSqlsrv extends AdapterException
+{
+ /**
+ * Constructor
+ *
+ * If $message is an array, the assumption is that the return value of
+ * sqlsrv_errors() was provided. If so, it then retrieves the most recent
+ * error from that stack, and sets the message and code based on it.
+ *
+ * @param null|array|string $message
+ * @param null|int $code
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ if (is_array($message)) {
+ // Error should be array of errors
+ // We only need first one (?)
+ if (isset($message[0])) {
+ $message = $message[0];
+ }
+
+ $code = (int) $message['code'];
+ $message = (string) $message['message'];
+ }
+ parent::__construct($message, $code, new Exception($message, $code));
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Mysqli.php b/vendor/gipfl/zfdb/src/Adapter/Mysqli.php
new file mode 100644
index 0000000..15a191f
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Mysqli.php
@@ -0,0 +1,499 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter;
+
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Statement\Exception\StatementExceptionMysqli;
+use gipfl\ZfDb\Statement\MysqliStatement;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Mysqli extends Adapter
+{
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INT' => Db::INT_TYPE,
+ 'INTEGER' => Db::INT_TYPE,
+ 'MEDIUMINT' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'TINYINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'SERIAL' => Db::BIGINT_TYPE,
+ 'DEC' => Db::FLOAT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'DOUBLE' => Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Db::FLOAT_TYPE,
+ 'FIXED' => Db::FLOAT_TYPE,
+ 'FLOAT' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * @var MysqliStatement
+ */
+ protected $_stmt = null;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = MysqliStatement::class;
+
+ /**
+ * Quote a raw string.
+ *
+ * @param mixed $value Raw string
+ *
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $this->_connect();
+ return "'" . $this->_connection->real_escape_string($value) . "'";
+ }
+
+ /**
+ * Returns the symbol the adapter uses for delimiting identifiers.
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return "`";
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ * @throws StatementExceptionMysqli
+ */
+ public function listTables()
+ {
+ $result = array();
+ // Use mysqli extension API, because SHOW doesn't work
+ // well as a prepared statement on MySQL 4.1.
+ $sql = 'SHOW TABLES';
+ if ($queryResult = $this->getConnection()->query($sql)) {
+ while ($row = $queryResult->fetch_row()) {
+ $result[] = $row[0];
+ }
+ $queryResult->close();
+ } else {
+ throw new StatementExceptionMysqli($this->getConnection()->error);
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ /**
+ * @todo use INFORMATION_SCHEMA someday when
+ * MySQL's implementation isn't too slow.
+ */
+
+ if ($schemaName) {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
+ } else {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
+ }
+
+ /**
+ * Use mysqli extension API, because DESCRIBE doesn't work
+ * well as a prepared statement on MySQL 4.1.
+ */
+ if ($queryResult = $this->getConnection()->query($sql)) {
+ while ($row = $queryResult->fetch_assoc()) {
+ $result[] = $row;
+ }
+ $queryResult->close();
+ } else {
+ throw new StatementExceptionMysqli($this->getConnection()->error);
+ }
+
+ $desc = array();
+
+ $row_defaults = array(
+ 'Length' => null,
+ 'Scale' => null,
+ 'Precision' => null,
+ 'Unsigned' => null,
+ 'Primary' => false,
+ 'PrimaryPosition' => null,
+ 'Identity' => false
+ );
+ $i = 1;
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $row = array_merge($row_defaults, $row);
+ if (preg_match('/unsigned/', $row['Type'])) {
+ $row['Unsigned'] = true;
+ }
+ if (preg_match('/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = $matches[1];
+ $row['Length'] = $matches[2];
+ } elseif (preg_match('/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = 'decimal';
+ $row['Precision'] = $matches[1];
+ $row['Scale'] = $matches[2];
+ } elseif (preg_match('/^float\((\d+),(\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = 'float';
+ $row['Precision'] = $matches[1];
+ $row['Scale'] = $matches[2];
+ } elseif (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = $matches[1];
+ /**
+ * The optional argument of a MySQL int type is not precision
+ * or length; it is only a hint for display width.
+ */
+ }
+ if (strtoupper($row['Key']) == 'PRI') {
+ $row['Primary'] = true;
+ $row['PrimaryPosition'] = $p;
+ if ($row['Extra'] == 'auto_increment') {
+ $row['Identity'] = true;
+ } else {
+ $row['Identity'] = false;
+ }
+ ++$p;
+ }
+ $desc[$this->foldCase($row['Field'])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row['Field']),
+ 'COLUMN_POSITION' => $i,
+ 'DATA_TYPE' => $row['Type'],
+ 'DEFAULT' => $row['Default'],
+ 'NULLABLE' => (bool) ($row['Null'] == 'YES'),
+ 'LENGTH' => $row['Length'],
+ 'SCALE' => $row['Scale'],
+ 'PRECISION' => $row['Precision'],
+ 'UNSIGNED' => $row['Unsigned'],
+ 'PRIMARY' => $row['Primary'],
+ 'PRIMARY_POSITION' => $row['PrimaryPosition'],
+ 'IDENTITY' => $row['Identity']
+ );
+ ++$i;
+ }
+ return $desc;
+ }
+
+ /**
+ * Creates a connection to the database.
+ *
+ * @return void
+ * @throws StatementExceptionMysqli
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ if (!extension_loaded('mysqli')) {
+ throw new StatementExceptionMysqli(
+ 'The Mysqli extension is required for this adapter but the extension is not loaded'
+ );
+ }
+
+ if (isset($this->_config['port'])) {
+ $port = (integer) $this->_config['port'];
+ } else {
+ $port = null;
+ }
+
+ if (isset($this->_config['socket'])) {
+ $socket = $this->_config['socket'];
+ } else {
+ $socket = null;
+ }
+
+ $this->_connection = mysqli_init();
+
+ if (!empty($this->_config['driver_options'])) {
+ foreach ($this->_config['driver_options'] as $option => $value) {
+ if (is_string($option)) {
+ // Suppress warnings here
+ // Ignore it if it's not a valid constant
+ $option = @constant(strtoupper($option));
+ if ($option === null) {
+ continue;
+ }
+ }
+ mysqli_options($this->_connection, $option, $value);
+ }
+ }
+
+ // Suppress connection warnings here.
+ // Throw an exception instead.
+ $_isConnected = @mysqli_real_connect(
+ $this->_connection,
+ $this->_config['host'],
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['dbname'],
+ $port,
+ $socket
+ );
+
+ if ($_isConnected === false || mysqli_connect_errno()) {
+ $this->closeConnection();
+ throw new StatementExceptionMysqli(mysqli_connect_error());
+ }
+
+ if (!empty($this->_config['charset'])) {
+ mysqli_set_charset($this->_connection, $this->_config['charset']);
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) ($this->_connection instanceof mysqli));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ $this->_connection->close();
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Prepare a statement and return a PDOStatement-like object.
+ *
+ * @param string $sql SQL query
+ * @return MysqliStatement|bool
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ if ($this->_stmt) {
+ $this->_stmt->close();
+ }
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ if ($stmt === false) {
+ return false;
+ }
+ $stmt->setFetchMode($this->_fetchMode);
+ $this->_stmt = $stmt;
+ return $stmt;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * MySQL does not support sequences, so $tableName and $primaryKey are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @todo Return value should be int?
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $mysqli = $this->_connection;
+ return (string) $mysqli->insert_id;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->autocommit(false);
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->commit();
+ $this->_connection->autocommit(true);
+ }
+
+ /**
+ * Roll-back a transaction.
+ *
+ * @return void
+ */
+ protected function _rollBack()
+ {
+ $this->_connect();
+ $this->_connection->rollback();
+ $this->_connection->autocommit(true);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param int $mode
+ * @return void
+ * @throws StatementExceptionMysqli
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Db::FETCH_LAZY:
+ case Db::FETCH_ASSOC:
+ case Db::FETCH_NUM:
+ case Db::FETCH_BOTH:
+ case Db::FETCH_NAMED:
+ case Db::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ case Db::FETCH_BOUND: // bound to PHP variable
+ throw new StatementExceptionMysqli('FETCH_BOUND is not supported yet');
+ default:
+ throw new StatementExceptionMysqli("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param int $count
+ * @param int $offset OPTIONAL
+ * @return string
+ * @throws StatementExceptionMysqli
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new StatementExceptionMysqli("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new StatementExceptionMysqli("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'positional':
+ return true;
+ case 'named':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ *@return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $version = $this->_connection->server_version;
+ $major = (int) ($version / 10000);
+ $minor = (int) ($version % 10000 / 100);
+ $revision = (int) ($version % 100);
+ return $major . '.' . $minor . '.' . $revision;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Oracle.php b/vendor/gipfl/zfdb/src/Adapter/Oracle.php
new file mode 100644
index 0000000..3430834
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Oracle.php
@@ -0,0 +1,595 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterExceptionOracle;
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Expr;
+use gipfl\ZfDb\Statement\OracleStatement;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Oracle extends Adapter
+{
+ /**
+ * User-provided configuration.
+ *
+ * Basic keys are:
+ *
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * dbname => Either the name of the local Oracle instance, or the
+ * name of the entry in tnsnames.ora to which you want to connect.
+ * persistent => (boolean) Set TRUE to use a persistent connection
+ * @var array
+ */
+ protected $_config = array(
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ 'persistent' => false
+ );
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'BINARY_DOUBLE' => Db::FLOAT_TYPE,
+ 'BINARY_FLOAT' => Db::FLOAT_TYPE,
+ 'NUMBER' => Db::FLOAT_TYPE,
+ );
+
+ /**
+ * @var integer
+ */
+ protected $_execute_mode = null;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = OracleStatement::class;
+
+ /**
+ * Check if LOB field are returned as string
+ * instead of OCI-Lob object
+ *
+ * @var boolean
+ */
+ protected $_lobAsString = null;
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ * @throws AdapterExceptionOracle
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('oci8')) {
+ throw new AdapterExceptionOracle(
+ 'The OCI8 extension is required for this adapter but the extension is not loaded'
+ );
+ }
+
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+
+ $connectionFuncName = ($this->_config['persistent'] == true) ? 'oci_pconnect' : 'oci_connect';
+
+ $this->_connection = @$connectionFuncName(
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['dbname'],
+ $this->_config['charset']
+ );
+
+ // check the connection
+ if (!$this->_connection) {
+ throw new AdapterExceptionOracle(oci_error());
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) (is_resource($this->_connection)
+ && (get_resource_type($this->_connection) == 'oci8 connection'
+ || get_resource_type($this->_connection) == 'oci8 persistent connection')));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ oci_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Activate/deactivate return of LOB as string
+ *
+ * @param string $lobAsString
+ * @return Oracle
+ */
+ public function setLobAsString($lobAsString)
+ {
+ $this->_lobAsString = (bool) $lobAsString;
+ return $this;
+ }
+
+ /**
+ * Return whether or not LOB are returned as string
+ *
+ * @return boolean
+ */
+ public function getLobAsString()
+ {
+ if ($this->_lobAsString === null) {
+ // if never set by user, we use driver option if it exists otherwise false
+ if (isset($this->_config['driver_options']) &&
+ isset($this->_config['driver_options']['lob_as_string'])) {
+ $this->_lobAsString = (bool) $this->_config['driver_options']['lob_as_string'];
+ } else {
+ $this->_lobAsString = false;
+ }
+ }
+ return $this->_lobAsString;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return OracleStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ if ($stmt instanceof OracleStatement) {
+ $stmt->setLobAsString($this->getLobAsString());
+ }
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $value = str_replace("'", "''", $value);
+ return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
+ return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Oracle does not support IDENTITY columns, so if the sequence is not
+ * specified, this method returns null.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ // No support for IDENTITY columns; return null
+ return null;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ $data = $this->fetchCol('SELECT table_name FROM all_tables');
+ return $data;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $version = $this->getServerVersion();
+ if (($version === null) || version_compare($version, '9.0.0', '>=')) {
+ $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC
+ LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
+ ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME
+ AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
+ ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ } else {
+ $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
+ from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
+ WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
+ AND ACC.TABLE_NAME = AC.TABLE_NAME
+ AND ACC.OWNER = AC.OWNER
+ AND AC.CONSTRAINT_TYPE = 'P'
+ AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC, ($subSql) CC
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
+ AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+)
+ AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ }
+
+ $stmt = $this->query($sql, $bind);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ $table_name = 0;
+ $owner = 1;
+ $column_name = 2;
+ $data_type = 3;
+ $data_default = 4;
+ $nullable = 5;
+ $column_id = 6;
+ $data_length = 7;
+ $data_scale = 8;
+ $data_precision = 9;
+ $constraint_type = 10;
+ $position = 11;
+
+ $desc = array();
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$constraint_type] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$position];
+ /**
+ * Oracle does not support auto-increment keys.
+ */
+ $identity = false;
+ }
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$owner]),
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => $row[$column_id],
+ 'DATA_TYPE' => $row[$data_type],
+ 'DEFAULT' => $row[$data_default],
+ 'NULLABLE' => (bool) ($row[$nullable] == 'Y'),
+ 'LENGTH' => $row[$data_length],
+ 'SCALE' => $row[$data_scale],
+ 'PRECISION' => $row[$data_precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_setExecuteMode(OCI_DEFAULT);
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws AdapterExceptionOracle
+ */
+ protected function _commit()
+ {
+ if (!oci_commit($this->_connection)) {
+ throw new AdapterExceptionOracle(oci_error($this->_connection));
+ }
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws AdapterExceptionOracle
+ */
+ protected function _rollBack()
+ {
+ if (!oci_rollback($this->_connection)) {
+ throw new AdapterExceptionOracle(oci_error($this->_connection));
+ }
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode A fetch mode.
+ * @return void
+ * @throws AdapterExceptionOracle
+ *@todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Db::FETCH_NUM: // seq array
+ case Db::FETCH_ASSOC: // assoc array
+ case Db::FETCH_BOTH: // seq+assoc array
+ case Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Db::FETCH_BOUND: // bound to PHP variable
+ throw new AdapterExceptionOracle('FETCH_BOUND is not supported yet');
+ default:
+ throw new AdapterExceptionOracle("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws AdapterExceptionOracle
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterExceptionOracle("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterExceptionOracle("LIMIT argument offset=$offset is not valid");
+ }
+
+ /**
+ * Oracle does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ return $limit_sql;
+ }
+
+ /**
+ * @param integer $mode
+ * @throws AdapterExceptionOracle
+ */
+ private function _setExecuteMode($mode)
+ {
+ switch ($mode) {
+ case OCI_COMMIT_ON_SUCCESS:
+ case OCI_DEFAULT:
+ case OCI_DESCRIBE_ONLY:
+ $this->_execute_mode = $mode;
+ break;
+ default:
+ throw new AdapterExceptionOracle("Invalid execution mode '$mode' specified");
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function _getExecuteMode()
+ {
+ return $this->_execute_mode;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'named':
+ return true;
+ case 'positional':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $version = oci_server_version($this->_connection);
+ if ($version !== false) {
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm.php
new file mode 100644
index 0000000..c953349
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm.php
@@ -0,0 +1,352 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Adapter\Pdo\Ibm\Db2;
+use gipfl\ZfDb\Adapter\Pdo\Ibm\Ids;
+use gipfl\ZfDb\Db;
+use PDO;
+use PDOException;
+use PDOStatement;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Ibm extends PdoAdapter
+{
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'ibm';
+
+ /**
+ * The IBM data server connected to
+ *
+ * @var Db2|Ids
+ */
+ protected $_serverType = null;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INTEGER' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'DEC' => Db::FLOAT_TYPE,
+ 'REAL' => Db::FLOAT_TYPE,
+ 'NUMERIC' => Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Db::FLOAT_TYPE,
+ 'FLOAT' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * The IBM data server is set.
+ * Current options are DB2 or IDS
+ * @todo also differentiate between z/OS and i/5
+ *
+ * @return void
+ * @throws AdapterException
+ */
+ public function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+
+ $this->getConnection()->setAttribute(Db::ATTR_STRINGIFY_FETCHES, true);
+
+ try {
+ if ($this->_serverType === null) {
+ $server = substr($this->getConnection()->getAttribute(PDO::ATTR_SERVER_INFO), 0, 3);
+
+ switch ($server) {
+ case 'DB2':
+ $this->_serverType = new Db2($this);
+
+ // Add DB2-specific numeric types
+ $this->_numericDataTypes['DECFLOAT'] = Db::FLOAT_TYPE;
+ $this->_numericDataTypes['DOUBLE'] = Db::FLOAT_TYPE;
+ $this->_numericDataTypes['NUM'] = Db::FLOAT_TYPE;
+
+ break;
+ case 'IDS':
+ $this->_serverType = new Ids($this);
+
+ // Add IDS-specific numeric types
+ $this->_numericDataTypes['SERIAL'] = Db::INT_TYPE;
+ $this->_numericDataTypes['SERIAL8'] = Db::BIGINT_TYPE;
+ $this->_numericDataTypes['INT8'] = Db::BIGINT_TYPE;
+ $this->_numericDataTypes['SMALLFLOAT'] = Db::FLOAT_TYPE;
+ $this->_numericDataTypes['MONEY'] = Db::FLOAT_TYPE;
+
+ break;
+ }
+ }
+ } catch (PDOException $e) {
+ /** @see AdapterException */
+ $error = strpos($e->getMessage(), 'driver does not support that attribute');
+ if ($error) {
+ throw new AdapterException(
+ "PDO_IBM driver extension is downlevel. Please use driver release version 1.2.1 or later",
+ 0,
+ $e
+ );
+ } else {
+ throw new AdapterException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ $this->_checkRequiredOptions($this->_config);
+
+ // check if using full connection string
+ if (array_key_exists('host', $this->_config)) {
+ $dsn = ';DATABASE=' . $this->_config['dbname']
+ . ';HOSTNAME=' . $this->_config['host']
+ . ';PORT=' . $this->_config['port']
+ // PDO_IBM supports only DB2 TCPIP protocol
+ . ';PROTOCOL=' . 'TCPIP;';
+ } else {
+ // catalogued connection
+ $dsn = $this->_config['dbname'];
+ }
+ return $this->_pdoType . ': ' . $dsn;
+ }
+
+ /**
+ * Checks required options
+ *
+ * @param array $config
+ * @throws AdapterException
+ * @return void
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ parent::_checkRequiredOptions($config);
+
+ if (array_key_exists('host', $this->_config) &&
+ !array_key_exists('port', $config)) {
+ throw new AdapterException("Configuration must have a key for 'port' when 'host' is specified");
+ }
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PDOStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ return $this->_serverType->listTables();
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $this->_connect();
+ return $this->_serverType->describeTable($tableName, $schemaName);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ * Special handling for PDO_IBM
+ * remove empty slots
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ */
+ public function insert($table, array $bind)
+ {
+ $this->_connect();
+ $newbind = array();
+ if (is_array($bind)) {
+ foreach ($bind as $name => $value) {
+ if ($value !== null) {
+ $newbind[$name] = $value;
+ }
+ }
+ }
+
+ return parent::insert($table, $newbind);
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $this->_connect();
+ return $this->_serverType->limit($sql, $count, $offset);
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT
+ * column.
+ *
+ * @param string $tableName OPTIONAL
+ * @param string $primaryKey OPTIONAL
+ * @return integer
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ $id = $this->getConnection()->lastInsertId();
+
+ return $id;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ return $this->_serverType->lastSequenceId($sequenceName);
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database,
+ * and return it.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ return $this->_serverType->nextSequenceId($sequenceName);
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Idm doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query(
+ 'SELECT service_level, fixpack_num FROM TABLE (sysproc.env_get_inst_info()) as INSTANCEINFO'
+ );
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+ if (count($result)) {
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $result[0][0], $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Db2.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Db2.php
new file mode 100644
index 0000000..7dea70a
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Db2.php
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo\Ibm;
+
+use gipfl\ZfDb\Adapter\Adapter;
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Db2
+{
+ /**
+ * @var Adapter
+ */
+ protected $_adapter = null;
+
+ /**
+ * Construct the data server class.
+ *
+ * It will be used to generate non-generic SQL
+ * for a particular data server
+ *
+ * @param Adapter $adapter
+ */
+ public function __construct($adapter)
+ {
+ $this->_adapter = $adapter;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT tabname "
+ . "FROM SYSCAT.TABLES ";
+ return $this->_adapter->fetchCol($sql);
+ }
+
+ /**
+ * DB2 catalog lookup for describe table
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE "
+ . $this->_adapter->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName);
+ if ($schemaName) {
+ $sql .= $this->_adapter->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName);
+ }
+ $sql .= " ORDER BY c.colno";
+
+ $desc = array();
+ $stmt = $this->_adapter->query($sql);
+
+ /**
+ * To avoid case issues, fetch using FETCH_NUM
+ */
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $nulls = 6;
+ $length = 7;
+ $scale = 8;
+ $identityCol = 9;
+ $tabconstype = 10;
+ $colseq = 11;
+
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$tabconstype] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$colseq];
+ }
+ /**
+ * In IBM DB2, an column can be IDENTITY
+ * even if it is not part of the PRIMARY KEY.
+ */
+ if ($row[$identityCol] == 'Y') {
+ $identity = true;
+ }
+
+ $desc[$this->_adapter->foldCase($row[$colname])] = array(
+ 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$colno]+1,
+ 'DATA_TYPE' => $row[$typename],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$nulls] == 'Y'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Adds a DB2-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws AdapterException
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count < 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ } else {
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0 && $count > 0) {
+ $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY";
+ return $limit_sql;
+ }
+ /**
+ * DB2 does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.*
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ }
+ return $limit_sql;
+ }
+
+ /**
+ * DB2-specific last sequence id
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT PREVVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * DB2-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT NEXTVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Ids.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Ids.php
new file mode 100644
index 0000000..730b3b2
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Ibm/Ids.php
@@ -0,0 +1,290 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo\Ibm;
+
+use gipfl\ZfDb\Adapter\Adapter;
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Ids
+{
+ /**
+ * @var Adapter
+ */
+ protected $_adapter = null;
+
+ /**
+ * Construct the data server class.
+ *
+ * It will be used to generate non-generic SQL
+ * for a particular data server
+ *
+ * @param Adapter $adapter
+ */
+ public function __construct($adapter)
+ {
+ $this->_adapter = $adapter;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT tabname "
+ . "FROM systables ";
+
+ return $this->_adapter->fetchCol($sql);
+ }
+
+ /**
+ * IDS catalog lookup for describe table
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // this is still a work in progress
+
+ $sql= "SELECT DISTINCT t.owner, t.tabname, c.colname, c.colno, c.coltype,
+ d.default, c.collength, t.tabid
+ FROM syscolumns c
+ JOIN systables t ON c.tabid = t.tabid
+ LEFT JOIN sysdefaults d ON c.tabid = d.tabid AND c.colno = d.colno
+ WHERE "
+ . $this->_adapter->quoteInto('UPPER(t.tabname) = UPPER(?)', $tableName);
+ if ($schemaName) {
+ $sql .= $this->_adapter->quoteInto(' AND UPPER(t.owner) = UPPER(?)', $schemaName);
+ }
+ $sql .= " ORDER BY c.colno";
+
+ $desc = array();
+ $stmt = $this->_adapter->query($sql);
+
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $length = 6;
+ $tabid = 7;
+
+ $primaryCols = null;
+
+ foreach ($result as $key => $row) {
+ $primary = false;
+ $primaryPosition = null;
+
+ if (!$primaryCols) {
+ $primaryCols = $this->_getPrimaryInfo($row[$tabid]);
+ }
+
+ if (array_key_exists($row[$colno], $primaryCols)) {
+ $primary = true;
+ $primaryPosition = $primaryCols[$row[$colno]];
+ }
+
+ $identity = false;
+ if ($row[$typename] == 6 + 256 ||
+ $row[$typename] == 18 + 256) {
+ $identity = true;
+ }
+
+ $desc[$this->_adapter->foldCase($row[$colname])] = array (
+ 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$colno],
+ 'DATA_TYPE' => $this->_getDataType($row[$typename]),
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) !($row[$typename] - 256 >= 0),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => ($row[$typename] == 5 ? $row[$length]&255 : 0),
+ 'PRECISION' => ($row[$typename] == 5 ? (int)($row[$length]/256) : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Map number representation of a data type
+ * to a string
+ *
+ * @param int $typeNo
+ * @return string
+ */
+ protected function _getDataType($typeNo)
+ {
+ $typemap = array(
+ 0 => "CHAR",
+ 1 => "SMALLINT",
+ 2 => "INTEGER",
+ 3 => "FLOAT",
+ 4 => "SMALLFLOAT",
+ 5 => "DECIMAL",
+ 6 => "SERIAL",
+ 7 => "DATE",
+ 8 => "MONEY",
+ 9 => "NULL",
+ 10 => "DATETIME",
+ 11 => "BYTE",
+ 12 => "TEXT",
+ 13 => "VARCHAR",
+ 14 => "INTERVAL",
+ 15 => "NCHAR",
+ 16 => "NVARCHAR",
+ 17 => "INT8",
+ 18 => "SERIAL8",
+ 19 => "SET",
+ 20 => "MULTISET",
+ 21 => "LIST",
+ 22 => "Unnamed ROW",
+ 40 => "Variable-length opaque type",
+ 4118 => "Named ROW"
+ );
+
+ if ($typeNo - 256 >= 0) {
+ $typeNo = $typeNo - 256;
+ }
+
+ return $typemap[$typeNo];
+ }
+
+ /**
+ * Helper method to retrieve primary key column
+ * and column location
+ *
+ * @param int $tabid
+ * @return array
+ */
+ protected function _getPrimaryInfo($tabid)
+ {
+ $sql = "SELECT i.part1, i.part2, i.part3, i.part4, i.part5, i.part6,
+ i.part7, i.part8, i.part9, i.part10, i.part11, i.part12,
+ i.part13, i.part14, i.part15, i.part16
+ FROM sysindexes i
+ JOIN sysconstraints c ON c.idxname = i.idxname
+ WHERE i.tabid = " . $tabid . " AND c.constrtype = 'P'";
+
+ $stmt = $this->_adapter->query($sql);
+ $results = $stmt->fetchAll();
+
+ $cols = array();
+
+ // this should return only 1 row
+ // unless there is no primary key,
+ // in which case, the empty array is returned
+ if ($results) {
+ $row = $results[0];
+ } else {
+ return $cols;
+ }
+
+ $position = 0;
+ foreach ($row as $key => $colno) {
+ $position++;
+ if ($colno == 0) {
+ return $cols;
+ } else {
+ $cols[$colno] = $position;
+ }
+ }
+
+ return $cols;
+ }
+
+ /**
+ * Adds an IDS-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws AdapterException
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count < 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ } elseif ($count == 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT * FROM (SELECT", $sql);
+ $limit_sql .= ") WHERE 0 = 1";
+ } else {
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+ if ($offset == 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT FIRST $count", $sql);
+ } else {
+ $limit_sql = str_ireplace("SELECT", "SELECT SKIP $offset LIMIT $count", $sql);
+ }
+ }
+ return $limit_sql;
+ }
+
+ /**
+ * IDS-specific last sequence id
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.CURRVAL FROM '
+ .'systables WHERE tabid = 1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * IDS-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.NEXTVAL FROM '
+ .'systables WHERE tabid = 1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Mssql.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Mssql.php
new file mode 100644
index 0000000..a75097c
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Mssql.php
@@ -0,0 +1,384 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+use PDOException;
+
+/**
+ * Class for connecting to Microsoft SQL Server databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Mssql extends PdoAdapter
+{
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'mssql';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INT' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'TINYINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'FLOAT' => Db::FLOAT_TYPE,
+ 'MONEY' => Db::FLOAT_TYPE,
+ 'NUMERIC' => Db::FLOAT_TYPE,
+ 'REAL' => Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ // don't pass the username and password in the DSN
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+
+ if (isset($dsn['port'])) {
+ $seperator = ':';
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ $seperator = ',';
+ }
+ $dsn['host'] .= $seperator . $dsn['port'];
+ unset($dsn['port']);
+ }
+
+ // this driver supports multiple DSN prefixes
+ // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
+ if (isset($dsn['pdoType'])) {
+ switch (strtolower($dsn['pdoType'])) {
+ case 'freetds':
+ case 'sybase':
+ $this->_pdoType = 'sybase';
+ break;
+ case 'mssql':
+ $this->_pdoType = 'mssql';
+ break;
+ case 'dblib':
+ default:
+ $this->_pdoType = 'dblib';
+ break;
+ }
+ unset($dsn['pdoType']);
+ }
+
+ // use all remaining parts in the DSN
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ $dsn = $this->_pdoType . ':' . implode(';', $dsn);
+ return $dsn;
+ }
+
+ /**
+ * @return void
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+ $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->exec('BEGIN TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->exec('COMMIT TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Roll-back a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _rollBack()
+ {
+ $this->_connect();
+ $this->_connection->exec('ROLLBACK TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * PRIMARY_AUTO => integer; position of auto-generated column in primary key
+ *
+ * @todo Discover column primary key position.
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ if ($schemaName != null) {
+ if (strpos($schemaName, '.') !== false) {
+ $result = explode('.', $schemaName);
+ $schemaName = $result[1];
+ }
+ }
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $primaryKeysResult = $stmt->fetchAll(Db::FETCH_NUM);
+ $primaryKeyColumn = array();
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = array();
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws AdapterException
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " OFFSET $offset ROWS FETCH NEXT $count ROWS ONLY";
+
+ return $sql;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Microsoft SQL Server does not support sequences, so the arguments to
+ * this method are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @throws AdapterException
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $sql = 'SELECT SCOPE_IDENTITY()';
+ return (int)$this->fetchOne($sql);
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Mssql doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR)");
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+ if (count($result)) {
+ return $result[0][0];
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (!is_int($value) && !is_float($value)) {
+ // Fix for null-byte injection
+ $value = addcslashes($value, "\000\032");
+ }
+ return parent::_quote($value);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Mysql.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Mysql.php
new file mode 100644
index 0000000..a84ae84
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Mysql.php
@@ -0,0 +1,258 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+
+/**
+ * Class for connecting to MySQL databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Mysql extends PdoAdapter
+{
+
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'mysql';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INT' => Db::INT_TYPE,
+ 'INTEGER' => Db::INT_TYPE,
+ 'MEDIUMINT' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'TINYINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'SERIAL' => Db::BIGINT_TYPE,
+ 'DEC' => Db::FLOAT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'DOUBLE' => Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Db::FLOAT_TYPE,
+ 'FIXED' => Db::FLOAT_TYPE,
+ 'FLOAT' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Override _dsn() and ensure that charset is incorporated in mysql
+ * @see PdoAdapter::_dsn()
+ */
+ protected function _dsn()
+ {
+ $dsn = parent::_dsn();
+ if (isset($this->_config['charset'])) {
+ $dsn .= ';charset=' . $this->_config['charset'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws AdapterException
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ if (!empty($this->_config['charset'])
+ && version_compare(PHP_VERSION, '5.3.6', '<')
+ ) {
+ $initCommand = "SET NAMES '" . $this->_config['charset'] . "'";
+ $this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND
+ }
+
+ parent::_connect();
+ }
+
+ /**
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return "`";
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ return $this->fetchCol('SHOW TABLES');
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // @todo use INFORMATION_SCHEMA someday when MySQL's
+ // implementation has reasonably good performance and
+ // the version with this improvement is in wide use.
+
+ if ($schemaName) {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
+ } else {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
+ }
+ $stmt = $this->query($sql);
+
+ // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ $field = 0;
+ $type = 1;
+ $null = 2;
+ $key = 3;
+ $default = 4;
+ $extra = 5;
+
+ $desc = array();
+ $i = 1;
+ $p = 1;
+ foreach ($result as $row) {
+ list($length, $scale, $precision, $unsigned, $primary, $primaryPosition, $identity)
+ = array(null, null, null, null, false, null, false);
+ if (preg_match('/unsigned/', $row[$type])) {
+ $unsigned = true;
+ }
+ if (preg_match('/^((?:var)?char)\((\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ $length = $matches[2];
+ } elseif (preg_match('/^decimal\((\d+),(\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = 'decimal';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ } elseif (preg_match('/^float\((\d+),(\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = 'float';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ } elseif (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ // The optional argument of a MySQL int type is not precision
+ // or length; it is only a hint for display width.
+ }
+ if (strtoupper($row[$key]) == 'PRI') {
+ $primary = true;
+ $primaryPosition = $p;
+ if ($row[$extra] == 'auto_increment') {
+ $identity = true;
+ } else {
+ $identity = false;
+ }
+ ++$p;
+ }
+ $desc[$this->foldCase($row[$field])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row[$field]),
+ 'COLUMN_POSITION' => $i,
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$null] == 'YES'),
+ 'LENGTH' => $length,
+ 'SCALE' => $scale,
+ 'PRECISION' => $precision,
+ 'UNSIGNED' => $unsigned,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ ++$i;
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws AdapterException
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Oci.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Oci.php
new file mode 100644
index 0000000..b76af36
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Oci.php
@@ -0,0 +1,367 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Adapter\Exception\AdapterExceptionOracle;
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Expr;
+
+/**
+ * Class for connecting to Oracle databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Oci extends PdoAdapter
+{
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'oci';
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Pdo_Oci';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'BINARY_DOUBLE' => Db::FLOAT_TYPE,
+ 'BINARY_FLOAT' => Db::FLOAT_TYPE,
+ 'NUMBER' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ if (isset($dsn['host'])) {
+ $tns = 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
+ '(HOST=' . $dsn['host'] . ')';
+
+ if (isset($dsn['port'])) {
+ $tns .= '(PORT=' . $dsn['port'] . ')';
+ } else {
+ $tns .= '(PORT=1521)';
+ }
+
+ $tns .= '))(CONNECT_DATA=(SID=' . $dsn['dbname'] . ')))';
+ } else {
+ $tns = 'dbname=' . $dsn['dbname'];
+ }
+
+ if (isset($dsn['charset'])) {
+ $tns .= ';charset=' . $dsn['charset'];
+ }
+
+ return $this->_pdoType . ':' . $tns;
+ }
+
+ /**
+ * Quote a raw string.
+ * Most PDO drivers have an implementation for the quote() method,
+ * but the Oracle OCI driver must use the same implementation as the
+ * Zend_Db_Adapter_Abstract class.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $value = str_replace("'", "''", $value);
+ return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
+ return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $data = $this->fetchCol('SELECT table_name FROM all_tables');
+ return $data;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $version = $this->getServerVersion();
+ if (($version === null) || version_compare($version, '9.0.0', '>=')) {
+ $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC
+ LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
+ ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME
+ AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
+ ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ } else {
+ $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
+ from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
+ WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
+ AND ACC.TABLE_NAME = AC.TABLE_NAME
+ AND ACC.OWNER = AC.OWNER
+ AND AC.CONSTRAINT_TYPE = 'P'
+ AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC, ($subSql) CC
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
+ AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+)
+ AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ }
+
+ $stmt = $this->query($sql, $bind);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ $table_name = 0;
+ $owner = 1;
+ $column_name = 2;
+ $data_type = 3;
+ $data_default = 4;
+ $nullable = 5;
+ $column_id = 6;
+ $data_length = 7;
+ $data_scale = 8;
+ $data_precision = 9;
+ $constraint_type = 10;
+ $position = 11;
+
+ $desc = array();
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$constraint_type] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$position];
+ /**
+ * Oracle does not support auto-increment keys.
+ */
+ $identity = false;
+ }
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$owner]),
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => $row[$column_id],
+ 'DATA_TYPE' => $row[$data_type],
+ 'DEFAULT' => $row[$data_default],
+ 'NULLABLE' => (bool) ($row[$nullable] == 'Y'),
+ 'LENGTH' => $row[$data_length],
+ 'SCALE' => $row[$data_scale],
+ 'PRECISION' => $row[$data_precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual');
+ return $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual');
+ return $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Oracle does not support IDENTITY columns, so if the sequence is not
+ * specified, this method returns null.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @throws AdapterExceptionOracle
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= $this->foldCase("_$primaryKey");
+ }
+ $sequenceName .= $this->foldCase('_seq');
+ return $this->lastSequenceId($sequenceName);
+ }
+ // No support for IDENTITY columns; return null
+ return null;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset
+ * @throws AdapterException
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ /**
+ * Oracle does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ return $limit_sql;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/PdoAdapter.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/PdoAdapter.php
new file mode 100644
index 0000000..8d84e5d
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/PdoAdapter.php
@@ -0,0 +1,367 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Adapter;
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Profiler;
+use gipfl\ZfDb\Select;
+use gipfl\ZfDb\Statement\PdoStatement;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+use PDO;
+use PDOException;
+
+/**
+ * Class for connecting to SQL databases and performing common operations using PDO.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class PdoAdapter extends Adapter
+{
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = PdoStatement::class;
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ // don't pass the username, password, charset, persistent and driver_options in the DSN
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['charset']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+
+ // use all remaining parts in the DSN
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ return $this->_pdoType . ':' . implode(';', $dsn);
+ }
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws AdapterException
+ */
+ protected function _connect()
+ {
+ // if we already have a PDO object, no need to re-connect.
+ if ($this->_connection) {
+ return;
+ }
+
+ // get the dsn first, because some adapters alter the $_pdoType
+ $dsn = $this->_dsn();
+
+ // check for PDO extension
+ if (!extension_loaded('pdo')) {
+ throw new AdapterException(
+ 'The PDO extension is required for this adapter but the extension is not loaded'
+ );
+ }
+
+ // check the PDO driver is available
+ if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
+ throw new AdapterException('The ' . $this->_pdoType . ' driver is not currently installed');
+ }
+
+ // create PDO connection
+ $q = $this->_profiler->queryStart('connect', Profiler::CONNECT);
+
+ // add the persistence flag if we find it in our config array
+ if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) {
+ $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
+ }
+
+ try {
+ $this->_connection = new PDO(
+ $dsn,
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+
+ $this->_profiler->queryEnd($q);
+
+ // set the PDO connection to perform case-folding on array keys, or not
+ $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
+
+ // always use exceptions.
+ $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ } catch (PDOException $e) {
+ $message = $e->getMessage();
+ if ($e->getPrevious() !== null && preg_match('~^SQLSTATE\[HY000\] \[\d{1,4}\]\s$~', $message)) {
+ // See https://bugs.php.net/bug.php?id=76604
+ $message .= $e->getPrevious()->getMessage();
+ }
+
+ throw new AdapterException($message, $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) ($this->_connection instanceof PDO));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ $this->_connection = null;
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PdoStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * On RDBMS brands that don't support sequences, $tableName and $primaryKey
+ * are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+ return $this->_connection->lastInsertId();
+ }
+
+ /**
+ * Special handling for PDO query().
+ * All bind parameter names must begin with ':'
+ *
+ * @param string|Select $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return Statement
+ * @throws AdapterException To re-throw PDOException.
+ */
+ public function query($sql, $bind = array())
+ {
+ if (empty($bind) && $sql instanceof Select) {
+ $bind = $sql->getBind();
+ }
+
+ if (is_array($bind)) {
+ foreach ($bind as $name => $value) {
+ if (!is_int($name) && !preg_match('/^:/', $name)) {
+ $newName = ":$name";
+ unset($bind[$name]);
+ $bind[$newName] = $value;
+ }
+ }
+ }
+
+ try {
+ return parent::query($sql, $bind);
+ } catch (PDOException $e) {
+ /**
+ * @see StatementException
+ */
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes an SQL statement and return the number of affected rows
+ *
+ * @param mixed $sql The SQL statement with placeholders.
+ * May be a string or Zend_Db_Select.
+ * @return integer Number of rows that were modified
+ * or deleted by the SQL statement
+ */
+ public function exec($sql)
+ {
+ if ($sql instanceof Select) {
+ $sql = $sql->assemble();
+ }
+
+ try {
+ $affected = $this->getConnection()->exec($sql);
+
+ if ($affected === false) {
+ $errorInfo = $this->getConnection()->errorInfo();
+ throw new AdapterException($errorInfo[2]);
+ }
+
+ return $affected;
+ } catch (PDOException $e) {
+ $code = $e->getCode();
+ $code = ctype_digit($code) ? (int) $code : 0;
+ throw new AdapterException($e->getMessage(), $code, $e);
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $this->_connect();
+ return $this->_connection->quote($value);
+ }
+
+ /**
+ * Begin a transaction.
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->beginTransaction();
+ }
+
+ /**
+ * Commit a transaction.
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->commit();
+ }
+
+ /**
+ * Roll-back a transaction.
+ */
+ protected function _rollBack()
+ {
+ $this->_connect();
+ $this->_connection->rollBack();
+ }
+
+ /**
+ * Set the PDO fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param int $mode A PDO fetch mode.
+ * @return void
+ * @throws AdapterException
+ */
+ public function setFetchMode($mode)
+ {
+ //check for PDO extension
+ if (!extension_loaded('pdo')) {
+ throw new AdapterException(
+ 'The PDO extension is required for this adapter but the extension is not loaded'
+ );
+ }
+ switch ($mode) {
+ case PDO::FETCH_LAZY:
+ case PDO::FETCH_ASSOC:
+ case PDO::FETCH_NUM:
+ case PDO::FETCH_BOTH:
+ case PDO::FETCH_NAMED:
+ case PDO::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ default:
+ throw new AdapterException("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'positional':
+ case 'named':
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ try {
+ $version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
+ } catch (PDOException $e) {
+ // In case of the driver doesn't support getting attributes
+ return null;
+ }
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Pgsql.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Pgsql.php
new file mode 100644
index 0000000..fc08d38
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Pgsql.php
@@ -0,0 +1,318 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+
+/**
+ * Class for connecting to PostgreSQL databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Pgsql extends PdoAdapter
+{
+
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'pgsql';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INTEGER' => Db::INT_TYPE,
+ 'SERIAL' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'BIGSERIAL' => Db::BIGINT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Db::FLOAT_TYPE,
+ 'NUMERIC' => Db::FLOAT_TYPE,
+ 'REAL' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws AdapterException
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ parent::_connect();
+
+ if (!empty($this->_config['charset'])) {
+ $sql = "SET NAMES '" . $this->_config['charset'] . "'";
+ $this->_connection->exec($sql);
+ }
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ // @todo use a better query with joins instead of subqueries
+ $sql = "SELECT c.relname AS table_name "
+ . "FROM pg_class c, pg_user u "
+ . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
+ . "AND c.relname !~ '^(pg_|sql_)' "
+ . "UNION "
+ . "SELECT c.relname AS table_name "
+ . "FROM pg_class c "
+ . "WHERE c.relkind = 'r' "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
+ . "AND c.relname !~ '^pg_'";
+
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = "SELECT
+ a.attnum,
+ n.nspname,
+ c.relname,
+ a.attname AS colname,
+ t.typname AS type,
+ a.atttypmod,
+ FORMAT_TYPE(a.atttypid, a.atttypmod) AS complete_type,
+ d.adsrc AS default_value,
+ a.attnotnull AS notnull,
+ a.attlen AS length,
+ co.contype,
+ ARRAY_TO_STRING(co.conkey, ',') AS conkey
+ FROM pg_attribute AS a
+ JOIN pg_class AS c ON a.attrelid = c.oid
+ JOIN pg_namespace AS n ON c.relnamespace = n.oid
+ JOIN pg_type AS t ON a.atttypid = t.oid
+ LEFT OUTER JOIN pg_constraint AS co ON (co.conrelid = c.oid
+ AND a.attnum = ANY(co.conkey) AND co.contype = 'p')
+ LEFT OUTER JOIN pg_attrdef AS d ON d.adrelid = c.oid AND d.adnum = a.attnum
+ WHERE a.attnum > 0 AND c.relname = ".$this->quote($tableName);
+ if ($schemaName) {
+ $sql .= " AND n.nspname = ".$this->quote($schemaName);
+ }
+ $sql .= ' ORDER BY a.attnum';
+
+ $stmt = $this->query($sql);
+
+ // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ $attnum = 0;
+ $nspname = 1;
+ $relname = 2;
+ $colname = 3;
+ $type = 4;
+ $atttypemod = 5;
+ $complete_type = 6;
+ $default_value = 7;
+ $notnull = 8;
+ $length = 9;
+ $contype = 10;
+ $conkey = 11;
+
+ $desc = array();
+ foreach ($result as $key => $row) {
+ $defaultValue = $row[$default_value];
+ if ($row[$type] == 'varchar' || $row[$type] == 'bpchar') {
+ if (preg_match('/character(?: varying)?(?:\((\d+)\))?/', $row[$complete_type], $matches)) {
+ if (isset($matches[1])) {
+ $row[$length] = $matches[1];
+ } else {
+ $row[$length] = null; // unlimited
+ }
+ }
+ if (preg_match("/^'(.*?)'::(?:character varying|bpchar)$/", $defaultValue, $matches)) {
+ $defaultValue = $matches[1];
+ }
+ }
+ list($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$contype] == 'p') {
+ $primary = true;
+ $primaryPosition = array_search($row[$attnum], explode(',', $row[$conkey])) + 1;
+ $identity = (bool) (preg_match('/^nextval/', $row[$default_value]));
+ }
+ $desc[$this->foldCase($row[$colname])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$nspname]),
+ 'TABLE_NAME' => $this->foldCase($row[$relname]),
+ 'COLUMN_NAME' => $this->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$attnum],
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $defaultValue,
+ 'NULLABLE' => (bool) ($row[$notnull] != 't'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => null, // @todo
+ 'PRECISION' => null, // @todo
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+ $value = $this->fetchOne("SELECT CURRVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ return $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+ $value = $this->fetchOne("SELECT NEXTVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ return $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+ return $this->_connection->lastInsertId($tableName);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Pdo/Sqlite.php b/vendor/gipfl/zfdb/src/Adapter/Pdo/Sqlite.php
new file mode 100644
index 0000000..5a12912
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Pdo/Sqlite.php
@@ -0,0 +1,293 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter\Pdo;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Db;
+
+/**
+ * Class for connecting to SQLite2 and SQLite3 databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Sqlite extends PdoAdapter
+{
+ /**
+ * PDO type
+ *
+ * @var string
+ */
+ protected $_pdoType = 'sqlite';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INTEGER' => Db::BIGINT_TYPE,
+ 'REAL' => Db::FLOAT_TYPE
+ );
+
+ /**
+ * Constructor.
+ *
+ * $config is an array of key/value pairs containing configuration
+ * options. Note that the SQLite options are different than most of
+ * the other PDO adapters in that no username or password are needed.
+ * Also, an extra config key "sqlite2" specifies compatibility mode.
+ *
+ * dbname => (string) The name of the database to user (required,
+ * use :memory: for memory-based database)
+ *
+ * sqlite2 => (boolean) PDO_SQLITE defaults to SQLite 3. For compatibility
+ * with an older SQLite 2 database, set this to TRUE.
+ *
+ * @param array $config An array of configuration keys.
+ */
+ public function __construct(array $config = array())
+ {
+ if (isset($config['sqlite2']) && $config['sqlite2']) {
+ $this->_pdoType = 'sqlite2';
+ }
+
+ // SQLite uses no username/password. Stub to satisfy parent::_connect()
+ $this->_config['username'] = null;
+ $this->_config['password'] = null;
+
+ return parent::__construct($config);
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws AdapterException
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ throw new AdapterException(
+ "Configuration array must have a key for 'dbname' that names the database instance"
+ );
+ }
+ }
+
+ /**
+ * DSN builder
+ */
+ protected function _dsn()
+ {
+ return $this->_pdoType .':'. $this->_config['dbname'];
+ }
+
+ /**
+ * Special configuration for SQLite behavior: make sure that result sets
+ * contain keys like 'column' instead of 'table.column'.
+ *
+ * @throws AdapterException
+ */
+ protected function _connect()
+ {
+ /**
+ * if we already have a PDO object, no need to re-connect.
+ */
+ if ($this->_connection) {
+ return;
+ }
+
+ parent::_connect();
+
+ $retval = $this->_connection->exec('PRAGMA full_column_names=0');
+ if ($retval === false) {
+ $error = $this->_connection->errorInfo();
+ throw new AdapterException($error[2]);
+ }
+
+ $retval = $this->_connection->exec('PRAGMA short_column_names=1');
+ if ($retval === false) {
+ $error = $this->_connection->errorInfo();
+ throw new AdapterException($error[2]);
+ }
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sqlite_master WHERE type='table' "
+ . "UNION ALL SELECT name FROM sqlite_temp_master "
+ . "WHERE type='table' ORDER BY name";
+
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = 'PRAGMA ';
+
+ if ($schemaName) {
+ $sql .= $this->quoteIdentifier($schemaName) . '.';
+ }
+
+ $sql .= 'table_info('.$this->quoteIdentifier($tableName).')';
+
+ $stmt = $this->query($sql);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ $cid = 0;
+ $name = 1;
+ $type = 2;
+ $notnull = 3;
+ $dflt_value = 4;
+ $pk = 5;
+
+ $desc = array();
+
+ $p = 1;
+ foreach ($result as $key => $row) {
+ list($length, $scale, $precision, $primary, $primaryPosition, $identity) =
+ array(null, null, null, false, null, false);
+ if (preg_match('/^((?:var)?char)\((\d+)\)/i', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ $length = $matches[2];
+ } elseif (preg_match('/^decimal\((\d+),(\d+)\)/i', $row[$type], $matches)) {
+ $row[$type] = 'DECIMAL';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ }
+ if ((bool) $row[$pk]) {
+ $primary = true;
+ $primaryPosition = $p;
+ /**
+ * SQLite INTEGER primary key is always auto-increment.
+ */
+ $identity = (bool) ($row[$type] == 'INTEGER');
+ ++$p;
+ }
+ $desc[$this->foldCase($row[$name])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($schemaName),
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row[$name]),
+ 'COLUMN_POSITION' => $row[$cid]+1,
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $row[$dflt_value],
+ 'NULLABLE' => ! (bool) $row[$notnull],
+ 'LENGTH' => $length,
+ 'SCALE' => $scale,
+ 'PRECISION' => $precision,
+ 'UNSIGNED' => null, // Sqlite3 does not support unsigned data
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (!is_int($value) && !is_float($value)) {
+ // Fix for null-byte injection
+ $value = addcslashes($value, "\000\032");
+ }
+ return parent::_quote($value);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Adapter/Sqlsrv.php b/vendor/gipfl/zfdb/src/Adapter/Sqlsrv.php
new file mode 100644
index 0000000..c940a75
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Adapter/Sqlsrv.php
@@ -0,0 +1,640 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Adapter;
+
+use gipfl\ZfDb\Adapter\Exception\AdapterException;
+use gipfl\ZfDb\Adapter\Exception\AdapterExceptionSqlsrv;
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Expr;
+use gipfl\ZfDb\Statement\SqlsrvStatement;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Sqlsrv extends Adapter
+{
+ /**
+ * User-provided configuration.
+ *
+ * Basic keys are:
+ *
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * dbname => The name of the local SQL Server instance
+ *
+ * @var array
+ */
+ protected $_config = array(
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ );
+
+ /**
+ * Last insert id from INSERT query
+ *
+ * @var int
+ */
+ protected $_lastInsertId;
+
+ /**
+ * Query used to fetch last insert id
+ *
+ * @var string
+ */
+ protected $_lastInsertSQL = 'SELECT SCOPE_IDENTITY() as Current_Identity';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Db::INT_TYPE => Db::INT_TYPE,
+ Db::BIGINT_TYPE => Db::BIGINT_TYPE,
+ Db::FLOAT_TYPE => Db::FLOAT_TYPE,
+ 'INT' => Db::INT_TYPE,
+ 'SMALLINT' => Db::INT_TYPE,
+ 'TINYINT' => Db::INT_TYPE,
+ 'BIGINT' => Db::BIGINT_TYPE,
+ 'DECIMAL' => Db::FLOAT_TYPE,
+ 'FLOAT' => Db::FLOAT_TYPE,
+ 'MONEY' => Db::FLOAT_TYPE,
+ 'NUMERIC' => Db::FLOAT_TYPE,
+ 'REAL' => Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Db::FLOAT_TYPE,
+ );
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = SqlsrvStatement::class;
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ * @throws AdapterExceptionSqlsrv
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('sqlsrv')) {
+ throw new AdapterExceptionSqlsrv(
+ 'The Sqlsrv extension is required for this adapter but the extension is not loaded'
+ );
+ }
+
+ $serverName = $this->_config['host'];
+ if (isset($this->_config['port'])) {
+ $port = (integer) $this->_config['port'];
+ $serverName .= ', ' . $port;
+ }
+
+ $connectionInfo = array(
+ 'Database' => $this->_config['dbname'],
+ );
+
+ if (isset($this->_config['username']) && isset($this->_config['password'])) {
+ $connectionInfo += array(
+ 'UID' => $this->_config['username'],
+ 'PWD' => $this->_config['password'],
+ );
+ }
+ // else - windows authentication
+
+ if (!empty($this->_config['driver_options'])) {
+ foreach ($this->_config['driver_options'] as $option => $value) {
+ // A value may be a constant.
+ if (is_string($value)) {
+ $constantName = strtoupper($value);
+ if (defined($constantName)) {
+ $connectionInfo[$option] = constant($constantName);
+ } else {
+ $connectionInfo[$option] = $value;
+ }
+ }
+ }
+ }
+
+ $this->_connection = sqlsrv_connect($serverName, $connectionInfo);
+
+ if (!$this->_connection) {
+ throw new AdapterExceptionSqlsrv(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws AdapterException
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ throw new AdapterException(
+ "Configuration array must have a key for 'dbname' that names the database instance"
+ );
+ }
+
+ if (! array_key_exists('password', $config) && array_key_exists('username', $config)) {
+ throw new AdapterException(
+ "Configuration array must have a key for 'password' for login credentials."
+ . " If Windows Authentication is desired, both keys 'username' and 'password'"
+ . " should be ommited from config."
+ );
+ }
+
+ if (array_key_exists('password', $config) && !array_key_exists('username', $config)) {
+ throw new AdapterException(
+ "Configuration array must have a key for 'username' for login credentials."
+ . " If Windows Authentication is desired, both keys 'username' and 'password'"
+ . " should be ommited from config."
+ );
+ }
+ }
+
+ /**
+ * Set the transaction isoltion level.
+ *
+ * @param integer|null $level A fetch mode from SQLSRV_TXN_*.
+ * @return true
+ * @throws AdapterExceptionSqlsrv
+ */
+ public function setTransactionIsolationLevel($level = null)
+ {
+ $this->_connect();
+ $sql = null;
+
+ // Default transaction level in sql server
+ if ($level === null) {
+ $level = SQLSRV_TXN_READ_COMMITTED;
+ }
+
+ switch ($level) {
+ case SQLSRV_TXN_READ_UNCOMMITTED:
+ $sql = "READ UNCOMMITTED";
+ break;
+ case SQLSRV_TXN_READ_COMMITTED:
+ $sql = "READ COMMITTED";
+ break;
+ case SQLSRV_TXN_REPEATABLE_READ:
+ $sql = "REPEATABLE READ";
+ break;
+ case SQLSRV_TXN_SNAPSHOT:
+ $sql = "SNAPSHOT";
+ break;
+ case SQLSRV_TXN_SERIALIZABLE:
+ $sql = "SERIALIZABLE";
+ break;
+ default:
+ throw new AdapterExceptionSqlsrv("Invalid transaction isolation level mode '$level' specified");
+ }
+
+ if (!sqlsrv_query($this->_connection, "SET TRANSACTION ISOLATION LEVEL $sql;")) {
+ throw new AdapterExceptionSqlsrv("Transaction cannot be changed to '$level'");
+ }
+
+ return true;
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return (is_resource($this->_connection)
+ && (get_resource_type($this->_connection) == 'SQL Server Connection')
+ );
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ sqlsrv_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return SqlsrvStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value)) {
+ return $value;
+ } elseif (is_float($value)) {
+ return sprintf('%F', $value);
+ }
+
+ $value = addcslashes($value, "\000\032");
+ return "'" . str_replace("'", "''", $value) . "'";
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName) {
+ $tableName = $this->quote($tableName);
+ $sql = 'SELECT IDENT_CURRENT (' . $tableName . ') as Current_Identity';
+ return (string) $this->fetchOne($sql);
+ }
+
+ if ($this->_lastInsertId > 0) {
+ return (string) $this->_lastInsertId;
+ }
+
+ $sql = $this->_lastInsertSQL;
+ return (string) $this->fetchOne($sql);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ */
+ public function insert($table, array $bind)
+ {
+ // extract and quote col names from the array keys
+ $cols = array();
+ $vals = array();
+ foreach ($bind as $col => $val) {
+ $cols[] = $this->quoteIdentifier($col, true);
+ if ($val instanceof Expr) {
+ $vals[] = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ $vals[] = '?';
+ }
+ }
+
+ // build the statement
+ $sql = "INSERT INTO "
+ . $this->quoteIdentifier($table, true)
+ . ' (' . implode(', ', $cols) . ') '
+ . 'VALUES (' . implode(', ', $vals) . ')'
+ . ' ' . $this->_lastInsertSQL;
+
+ // execute the statement and return the number of affected rows
+ $stmt = $this->query($sql, array_values($bind));
+ $result = $stmt->rowCount();
+
+ $stmt->nextRowset();
+
+ $this->_lastInsertId = $stmt->fetchColumn();
+
+ return $result;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Db::FETCH_NUM);
+
+ // ZF-7698
+ $stmt->closeCursor();
+
+ if (count($result) == 0) {
+ return array();
+ }
+
+ $owner = 1;
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $tableOwner = $result[0][$owner];
+ $sql = "exec sp_pkeys @table_owner = " . $tableOwner
+ . ", @table_name = " . $this->quoteIdentifier($tableName, true);
+ $stmt = $this->query($sql);
+
+ $primaryKeysResult = $stmt->fetchAll(Db::FETCH_NUM);
+ $primaryKeyColumn = array();
+
+ // Per http://msdn.microsoft.com/en-us/library/ms189813.aspx,
+ // results from sp_keys stored procedure are:
+ // 0=TABLE_QUALIFIER 1=TABLE_OWNER 2=TABLE_NAME 3=COLUMN_NAME 4=KEY_SEQ 5=PK_NAME
+
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = array();
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity,
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return void
+ * @throws AdapterExceptionSqlsrv
+ */
+ protected function _beginTransaction()
+ {
+ if (!sqlsrv_begin_transaction($this->_connection)) {
+ throw new AdapterExceptionSqlsrv(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws AdapterExceptionSqlsrv
+ */
+ protected function _commit()
+ {
+ if (!sqlsrv_commit($this->_connection)) {
+ throw new AdapterExceptionSqlsrv(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws AdapterExceptionSqlsrv
+ */
+ protected function _rollBack()
+ {
+ if (!sqlsrv_rollback($this->_connection)) {
+ throw new AdapterExceptionSqlsrv(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode A fetch mode.
+ * @return void
+ * @throws AdapterExceptionSqlsrv
+ *@todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Db::FETCH_NUM: // seq array
+ case Db::FETCH_ASSOC: // assoc array
+ case Db::FETCH_BOTH: // seq+assoc array
+ case Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Db::FETCH_BOUND: // bound to PHP variable
+ throw new AdapterExceptionSqlsrv('FETCH_BOUND is not supported yet');
+ default:
+ throw new AdapterExceptionSqlsrv("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws AdapterException
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new AdapterException("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see AdapterException */
+ throw new AdapterException("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0) {
+ $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $sql);
+ } else {
+ $orderby = stristr($sql, 'ORDER BY');
+
+ if (!$orderby) {
+ $over = 'ORDER BY (SELECT 0)';
+ } else {
+ $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby);
+ }
+
+ // Remove ORDER BY clause from $sql
+ $sql = preg_replace('/\s+ORDER BY(.*)/', '', $sql);
+
+ // Add ORDER BY clause as an argument for ROW_NUMBER()
+ $sql = "SELECT ROW_NUMBER() OVER ($over) AS \"ZEND_DB_ROWNUM\", * FROM ($sql) AS inner_tbl";
+
+ $start = $offset + 1;
+
+ if ($count == PHP_INT_MAX) {
+ $sql = "WITH outer_tbl AS ($sql) SELECT * FROM outer_tbl WHERE \"ZEND_DB_ROWNUM\" >= $start";
+ } else {
+ $end = $offset + $count;
+ $sql = "WITH outer_tbl AS ($sql) SELECT * FROM outer_tbl"
+ . " WHERE \"ZEND_DB_ROWNUM\" BETWEEN $start AND $end";
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ if ($type == 'positional') {
+ return true;
+ }
+
+ // if its 'named' or anything else
+ return false;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $serverInfo = sqlsrv_server_info($this->_connection);
+
+ if ($serverInfo !== false) {
+ return $serverInfo['SQLServerVersion'];
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Db.php b/vendor/gipfl/zfdb/src/Db.php
new file mode 100644
index 0000000..529461b
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Db.php
@@ -0,0 +1,248 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb;
+
+use gipfl\ZfDb\Adapter\Adapter;
+use gipfl\ZfDb\Exception\DbException;
+
+/**
+ * Class for connecting to SQL databases and performing common operations.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Db
+{
+ /**
+ * Use the PROFILER constant in the config of a Zend_Db_Adapter.
+ */
+ const PROFILER = 'profiler';
+
+ /**
+ * Use the CASE_FOLDING constant in the config of a Zend_Db_Adapter.
+ */
+ const CASE_FOLDING = 'caseFolding';
+
+ /**
+ * Use the FETCH_MODE constant in the config of a Zend_Db_Adapter.
+ */
+ const FETCH_MODE = 'fetchMode';
+
+ /**
+ * Use the AUTO_QUOTE_IDENTIFIERS constant in the config of a Zend_Db_Adapter.
+ */
+ const AUTO_QUOTE_IDENTIFIERS = 'autoQuoteIdentifiers';
+
+ /**
+ * Use the ALLOW_SERIALIZATION constant in the config of a Zend_Db_Adapter.
+ */
+ const ALLOW_SERIALIZATION = 'allowSerialization';
+
+ /**
+ * Use the AUTO_RECONNECT_ON_UNSERIALIZE constant in the config of a Zend_Db_Adapter.
+ */
+ const AUTO_RECONNECT_ON_UNSERIALIZE = 'autoReconnectOnUnserialize';
+
+ /**
+ * Use the INT_TYPE, BIGINT_TYPE, and FLOAT_TYPE with the quote() method.
+ */
+ const INT_TYPE = 0;
+ const BIGINT_TYPE = 1;
+ const FLOAT_TYPE = 2;
+
+ /**
+ * PDO constant values discovered by this script result:
+ *
+ * $list = array(
+ * 'PARAM_BOOL', 'PARAM_NULL', 'PARAM_INT', 'PARAM_STR', 'PARAM_LOB',
+ * 'PARAM_STMT', 'PARAM_INPUT_OUTPUT', 'FETCH_LAZY', 'FETCH_ASSOC',
+ * 'FETCH_NUM', 'FETCH_BOTH', 'FETCH_OBJ', 'FETCH_BOUND',
+ * 'FETCH_COLUMN', 'FETCH_CLASS', 'FETCH_INTO', 'FETCH_FUNC',
+ * 'FETCH_GROUP', 'FETCH_UNIQUE', 'FETCH_CLASSTYPE', 'FETCH_SERIALIZE',
+ * 'FETCH_NAMED', 'ATTR_AUTOCOMMIT', 'ATTR_PREFETCH', 'ATTR_TIMEOUT',
+ * 'ATTR_ERRMODE', 'ATTR_SERVER_VERSION', 'ATTR_CLIENT_VERSION',
+ * 'ATTR_SERVER_INFO', 'ATTR_CONNECTION_STATUS', 'ATTR_CASE',
+ * 'ATTR_CURSOR_NAME', 'ATTR_CURSOR', 'ATTR_ORACLE_NULLS',
+ * 'ATTR_PERSISTENT', 'ATTR_STATEMENT_CLASS', 'ATTR_FETCH_TABLE_NAMES',
+ * 'ATTR_FETCH_CATALOG_NAMES', 'ATTR_DRIVER_NAME',
+ * 'ATTR_STRINGIFY_FETCHES', 'ATTR_MAX_COLUMN_LEN', 'ERRMODE_SILENT',
+ * 'ERRMODE_WARNING', 'ERRMODE_EXCEPTION', 'CASE_NATURAL',
+ * 'CASE_LOWER', 'CASE_UPPER', 'NULL_NATURAL', 'NULL_EMPTY_STRING',
+ * 'NULL_TO_STRING', 'ERR_NONE', 'FETCH_ORI_NEXT',
+ * 'FETCH_ORI_PRIOR', 'FETCH_ORI_FIRST', 'FETCH_ORI_LAST',
+ * 'FETCH_ORI_ABS', 'FETCH_ORI_REL', 'CURSOR_FWDONLY', 'CURSOR_SCROLL',
+ * 'ERR_CANT_MAP', 'ERR_SYNTAX', 'ERR_CONSTRAINT', 'ERR_NOT_FOUND',
+ * 'ERR_ALREADY_EXISTS', 'ERR_NOT_IMPLEMENTED', 'ERR_MISMATCH',
+ * 'ERR_TRUNCATED', 'ERR_DISCONNECTED', 'ERR_NO_PERM',
+ * );
+ *
+ * $const = array();
+ * foreach ($list as $name) {
+ * $const[$name] = constant("PDO::$name");
+ * }
+ * var_export($const);
+ */
+ const ATTR_AUTOCOMMIT = 0;
+ const ATTR_CASE = 8;
+ const ATTR_CLIENT_VERSION = 5;
+ const ATTR_CONNECTION_STATUS = 7;
+ const ATTR_CURSOR = 10;
+ const ATTR_CURSOR_NAME = 9;
+ const ATTR_DRIVER_NAME = 16;
+ const ATTR_ERRMODE = 3;
+ const ATTR_FETCH_CATALOG_NAMES = 15;
+ const ATTR_FETCH_TABLE_NAMES = 14;
+ const ATTR_MAX_COLUMN_LEN = 18;
+ const ATTR_ORACLE_NULLS = 11;
+ const ATTR_PERSISTENT = 12;
+ const ATTR_PREFETCH = 1;
+ const ATTR_SERVER_INFO = 6;
+ const ATTR_SERVER_VERSION = 4;
+ const ATTR_STATEMENT_CLASS = 13;
+ const ATTR_STRINGIFY_FETCHES = 17;
+ const ATTR_TIMEOUT = 2;
+ const CASE_LOWER = 2;
+ const CASE_NATURAL = 0;
+ const CASE_UPPER = 1;
+ const CURSOR_FWDONLY = 0;
+ const CURSOR_SCROLL = 1;
+ const ERR_ALREADY_EXISTS = null;
+ const ERR_CANT_MAP = null;
+ const ERR_CONSTRAINT = null;
+ const ERR_DISCONNECTED = null;
+ const ERR_MISMATCH = null;
+ const ERR_NO_PERM = null;
+ const ERR_NONE = '00000';
+ const ERR_NOT_FOUND = null;
+ const ERR_NOT_IMPLEMENTED = null;
+ const ERR_SYNTAX = null;
+ const ERR_TRUNCATED = null;
+ const ERRMODE_EXCEPTION = 2;
+ const ERRMODE_SILENT = 0;
+ const ERRMODE_WARNING = 1;
+ const FETCH_ASSOC = 2;
+ const FETCH_BOTH = 4;
+ const FETCH_BOUND = 6;
+ const FETCH_CLASS = 8;
+ const FETCH_CLASSTYPE = 262144;
+ const FETCH_COLUMN = 7;
+ const FETCH_FUNC = 10;
+ const FETCH_GROUP = 65536;
+ const FETCH_INTO = 9;
+ const FETCH_LAZY = 1;
+ const FETCH_NAMED = 11;
+ const FETCH_NUM = 3;
+ const FETCH_OBJ = 5;
+ const FETCH_ORI_ABS = 4;
+ const FETCH_ORI_FIRST = 2;
+ const FETCH_ORI_LAST = 3;
+ const FETCH_ORI_NEXT = 0;
+ const FETCH_ORI_PRIOR = 1;
+ const FETCH_ORI_REL = 5;
+ const FETCH_SERIALIZE = 524288;
+ const FETCH_UNIQUE = 196608;
+ const NULL_EMPTY_STRING = 1;
+ const NULL_NATURAL = 0;
+ const NULL_TO_STRING = null;
+ const PARAM_BOOL = 5;
+ const PARAM_INPUT_OUTPUT = -2147483648;
+ const PARAM_INT = 1;
+ const PARAM_LOB = 3;
+ const PARAM_NULL = 0;
+ const PARAM_STMT = 4;
+ const PARAM_STR = 2;
+
+ /**
+ * Factory for Zend_Db_Adapter_Abstract classes.
+ *
+ * First argument may be a string containing the base of the adapter class
+ * name, e.g. 'Mysqli' corresponds to class Zend_Db_Adapter_Mysqli. This
+ * name is currently case-insensitive, but is not ideal to rely on this behavior.
+ * If your class is named 'My_Company_Pdo_Mysql', where 'My_Company' is the namespace
+ * and 'Pdo_Mysql' is the adapter name, it is best to use the name exactly as it
+ * is defined in the class. This will ensure proper use of the factory API.
+ *
+ * First argument may alternatively be an object of type Zend_Config.
+ * The adapter class base name is read from the 'adapter' property.
+ * The adapter config parameters are read from the 'params' property.
+ *
+ * Second argument is optional and may be an associative array of key-value
+ * pairs. This is used as the argument to the adapter constructor.
+ *
+ * If the first argument is of type Zend_Config, it is assumed to contain
+ * all parameters, and the second argument is ignored.
+ *
+ * @param mixed $adapter String name of base adapter class, or Zend_Config object.
+ * @param mixed $config OPTIONAL; an array or Zend_Config object with adapter parameters.
+ * @return Adapter
+ * @throws DbException
+ */
+ public static function factory($adapter, $config = array())
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ }
+
+ /*
+ * Convert Zend_Config argument to plain string
+ * adapter name and separate config object.
+ */
+ if ($adapter instanceof Zend_Config) {
+ if (isset($adapter->params)) {
+ $config = $adapter->params->toArray();
+ }
+ if (isset($adapter->adapter)) {
+ $adapter = (string) $adapter->adapter;
+ } else {
+ $adapter = null;
+ }
+ }
+
+ /*
+ * Verify that adapter parameters are in an array.
+ */
+ if (!is_array($config)) {
+ throw new DbException('Adapter parameters must be in an array or a Zend_Config object');
+ }
+
+ /*
+ * Verify that an adapter name has been specified.
+ */
+ if (!is_string($adapter) || empty($adapter)) {
+ throw new DbException('Adapter name must be specified in a string');
+ }
+
+ /*
+ * Form full adapter class name
+ */
+ $adapterClass = '\\gipfl\\ZfDb\\Adapter\\'
+ . str_replace(' ', '\\', ucwords(str_replace('_', ' ', strtolower($adapter))));
+ $dbAdapter = new $adapterClass($config);
+
+ /*
+ * Verify that the object created is a descendent of the abstract adapter type.
+ */
+ if (! $dbAdapter instanceof Adapter) {
+ throw new DbException("Adapter class '$adapterClass' does not extend gipfl\ZfDb\Abstract");
+ }
+
+ return $dbAdapter;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Exception/DbException.php b/vendor/gipfl/zfdb/src/Exception/DbException.php
new file mode 100644
index 0000000..ab8c740
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Exception/DbException.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Exception;
+
+use Exception;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class DbException extends Exception
+{
+}
diff --git a/vendor/gipfl/zfdb/src/Exception/SelectException.php b/vendor/gipfl/zfdb/src/Exception/SelectException.php
new file mode 100644
index 0000000..8764741
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Exception/SelectException.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Exception;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class SelectException extends DbException
+{
+}
diff --git a/vendor/gipfl/zfdb/src/Expr.php b/vendor/gipfl/zfdb/src/Expr.php
new file mode 100644
index 0000000..2184c5a
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Expr.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb;
+
+/**
+ * Class for SQL SELECT fragments.
+ *
+ * This class simply holds a string, so that fragments of SQL statements can be
+ * distinguished from identifiers and values that should be implicitly quoted
+ * when interpolated into SQL statements.
+ *
+ * For example, when specifying a primary key value when inserting into a new
+ * row, some RDBMS brands may require you to use an expression to generate the
+ * new value of a sequence. If this expression is treated as an identifier,
+ * it will be quoted and the expression will not be evaluated. Another example
+ * is that you can use Expr in the Select::order() method to
+ * order by an expression instead of simply a column name.
+ *
+ * The way this works is that in each context in which a column name can be
+ * specified to methods of Zend_Db classes, if the value is an instance of
+ * Expr instead of a plain string, then the expression is not quoted.
+ * If it is a plain string, it is assumed to be a plain column name.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Expr
+{
+ /**
+ * Storage for the SQL expression.
+ *
+ * @var string
+ */
+ protected $_expression;
+
+ /**
+ * Instantiate an expression, which is just a string stored as
+ * an instance member variable.
+ *
+ * @param string $expression The string containing a SQL expression.
+ */
+ public function __construct($expression)
+ {
+ $this->_expression = (string) $expression;
+ }
+
+ /**
+ * @return string The string of the SQL expression stored in this object.
+ */
+ public function __toString()
+ {
+ return $this->_expression;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Profiler.php b/vendor/gipfl/zfdb/src/Profiler.php
new file mode 100644
index 0000000..026fa0e
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Profiler.php
@@ -0,0 +1,463 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb;
+
+use gipfl\ZfDb\Profiler\ProfilerException;
+use gipfl\ZfDb\Profiler\ProfilerQuery;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Profiler
+{
+ /**
+ * A connection operation or selecting a database.
+ */
+ const CONNECT = 1;
+
+ /**
+ * Any general database query that does not fit into the other constants.
+ */
+ const QUERY = 2;
+
+ /**
+ * Adding new data to the database, such as SQL's INSERT.
+ */
+ const INSERT = 4;
+
+ /**
+ * Updating existing information in the database, such as SQL's UPDATE.
+ *
+ */
+ const UPDATE = 8;
+
+ /**
+ * An operation related to deleting data in the database,
+ * such as SQL's DELETE.
+ */
+ const DELETE = 16;
+
+ /**
+ * Retrieving information from the database, such as SQL's SELECT.
+ */
+ const SELECT = 32;
+
+ /**
+ * Transactional operation, such as start transaction, commit, or rollback.
+ */
+ const TRANSACTION = 64;
+
+ /**
+ * Inform that a query is stored (in case of filtering)
+ */
+ const STORED = 'stored';
+
+ /**
+ * Inform that a query is ignored (in case of filtering)
+ */
+ const IGNORED = 'ignored';
+
+ /**
+ * Array of ProfilerQuery objects.
+ *
+ * @var array
+ */
+ protected $_queryProfiles = array();
+
+ /**
+ * Stores enabled state of the profiler. If set to False, calls to
+ * queryStart() will simply be ignored.
+ *
+ * @var boolean
+ */
+ protected $_enabled = false;
+
+ /**
+ * Stores the number of seconds to filter. NULL if filtering by time is
+ * disabled. If an integer is stored here, profiles whose elapsed time
+ * is less than this value in seconds will be unset from
+ * the self::$_queryProfiles array.
+ *
+ * @var integer
+ */
+ protected $_filterElapsedSecs = null;
+
+ /**
+ * Logical OR of any of the filter constants. NULL if filtering by query
+ * type is disable. If an integer is stored here, it is the logical OR of
+ * any of the query type constants. When the query ends, if it is not
+ * one of the types specified, it will be unset from the
+ * self::$_queryProfiles array.
+ *
+ * @var integer
+ */
+ protected $_filterTypes = null;
+
+ /**
+ * Class constructor. The profiler is disabled by default unless it is
+ * specifically enabled by passing in $enabled here or calling setEnabled().
+ *
+ * @param boolean $enabled
+ * @return void
+ */
+ public function __construct($enabled = false)
+ {
+ $this->setEnabled($enabled);
+ }
+
+ /**
+ * Enable or disable the profiler. If $enable is false, the profiler
+ * is disabled and will not log any queries sent to it.
+ *
+ * @param boolean $enable
+ * @return Profiler Provides a fluent interface
+ */
+ public function setEnabled($enable)
+ {
+ $this->_enabled = (boolean) $enable;
+
+ return $this;
+ }
+
+ /**
+ * Get the current state of enable. If True is returned,
+ * the profiler is enabled.
+ *
+ * @return boolean
+ */
+ public function getEnabled()
+ {
+ return $this->_enabled;
+ }
+
+ /**
+ * Sets a minimum number of seconds for saving query profiles. If this
+ * is set, only those queries whose elapsed time is equal or greater than
+ * $minimumSeconds will be saved. To save all queries regardless of
+ * elapsed time, set $minimumSeconds to null.
+ *
+ * @param integer $minimumSeconds OPTIONAL
+ * @return Profiler Provides a fluent interface
+ */
+ public function setFilterElapsedSecs($minimumSeconds = null)
+ {
+ if (null === $minimumSeconds) {
+ $this->_filterElapsedSecs = null;
+ } else {
+ $this->_filterElapsedSecs = (integer) $minimumSeconds;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the minimum number of seconds for saving query profiles, or null if
+ * query profiles are saved regardless of elapsed time.
+ *
+ * @return integer|null
+ */
+ public function getFilterElapsedSecs()
+ {
+ return $this->_filterElapsedSecs;
+ }
+
+ /**
+ * Sets the types of query profiles to save. Set $queryType to one of
+ * the Zend_Db_Profiler::* constants to only save profiles for that type of
+ * query. To save more than one type, logical OR them together. To
+ * save all queries regardless of type, set $queryType to null.
+ *
+ * @param integer $queryTypes OPTIONAL
+ * @return Profiler Provides a fluent interface
+ */
+ public function setFilterQueryType($queryTypes = null)
+ {
+ $this->_filterTypes = $queryTypes;
+
+ return $this;
+ }
+
+ /**
+ * Returns the types of query profiles saved, or null if queries are saved regardless
+ * of their types.
+ *
+ * @return integer|null
+ * @see Profiler::setFilterQueryType()
+ */
+ public function getFilterQueryType()
+ {
+ return $this->_filterTypes;
+ }
+
+ /**
+ * Clears the history of any past query profiles. This is relentless
+ * and will even clear queries that were started and may not have
+ * been marked as ended.
+ *
+ * @return Profiler Provides a fluent interface
+ */
+ public function clear()
+ {
+ $this->_queryProfiles = array();
+
+ return $this;
+ }
+
+ /**
+ * Clone a profiler query
+ *
+ * @param ProfilerQuery $query
+ * @return integer or null
+ */
+ public function queryClone(ProfilerQuery $query)
+ {
+ $this->_queryProfiles[] = clone $query;
+
+ end($this->_queryProfiles);
+
+ return key($this->_queryProfiles);
+ }
+
+ /**
+ * Starts a query. Creates a new query profile object (ProfilerQuery)
+ * and returns the "query profiler handle". Run the query, then call
+ * queryEnd() and pass it this handle to make the query as ended and
+ * record the time. If the profiler is not enabled, this takes no
+ * action and immediately returns null.
+ *
+ * @param string $queryText SQL statement
+ * @param integer $queryType OPTIONAL Type of query, one of the Zend_Db_Profiler::* constants
+ * @return integer|null
+ */
+ public function queryStart($queryText, $queryType = null)
+ {
+ if (!$this->_enabled) {
+ return null;
+ }
+
+ // make sure we have a query type
+ if (null === $queryType) {
+ switch (strtolower(substr(ltrim($queryText), 0, 6))) {
+ case 'insert':
+ $queryType = self::INSERT;
+ break;
+ case 'update':
+ $queryType = self::UPDATE;
+ break;
+ case 'delete':
+ $queryType = self::DELETE;
+ break;
+ case 'select':
+ $queryType = self::SELECT;
+ break;
+ default:
+ $queryType = self::QUERY;
+ break;
+ }
+ }
+
+ /**
+ * @see ProfilerQuery
+ */
+ $this->_queryProfiles[] = new ProfilerQuery($queryText, $queryType);
+
+ end($this->_queryProfiles);
+
+ return key($this->_queryProfiles);
+ }
+
+ /**
+ * Ends a query. Pass it the handle that was returned by queryStart().
+ * This will mark the query as ended and save the time.
+ *
+ * @param integer $queryId
+ * @throws ProfilerException
+ * @return string Inform that a query is stored or ignored.
+ */
+ public function queryEnd($queryId)
+ {
+ // Don't do anything if the Zend_Db_Profiler is not enabled.
+ if (!$this->_enabled) {
+ return self::IGNORED;
+ }
+
+ // Check for a valid query handle.
+ if (!isset($this->_queryProfiles[$queryId])) {
+ /**
+ * @see ProfilerException
+ */
+ throw new ProfilerException("Profiler has no query with handle '$queryId'.");
+ }
+
+ $qp = $this->_queryProfiles[$queryId];
+
+ // Ensure that the query profile has not already ended
+ if ($qp->hasEnded()) {
+ /**
+ * @see ProfilerException
+ */
+ throw new ProfilerException("Query with profiler handle '$queryId' has already ended.");
+ }
+
+ // End the query profile so that the elapsed time can be calculated.
+ $qp->end();
+
+ /**
+ * If filtering by elapsed time is enabled, only keep the profile if
+ * it ran for the minimum time.
+ */
+ if (null !== $this->_filterElapsedSecs && $qp->getElapsedSecs() < $this->_filterElapsedSecs) {
+ unset($this->_queryProfiles[$queryId]);
+ return self::IGNORED;
+ }
+
+ /**
+ * If filtering by query type is enabled, only keep the query if
+ * it was one of the allowed types.
+ */
+ if (null !== $this->_filterTypes && !($qp->getQueryType() & $this->_filterTypes)) {
+ unset($this->_queryProfiles[$queryId]);
+ return self::IGNORED;
+ }
+
+ return self::STORED;
+ }
+
+ /**
+ * Get a profile for a query. Pass it the same handle that was returned
+ * by queryStart() and it will return a ProfilerQuery object.
+ *
+ * @param integer $queryId
+ * @throws ProfilerException
+ * @return ProfilerQuery
+ */
+ public function getQueryProfile($queryId)
+ {
+ if (!array_key_exists($queryId, $this->_queryProfiles)) {
+ /**
+ * @see ProfilerException
+ */
+ throw new ProfilerException("Query handle '$queryId' not found in profiler log.");
+ }
+
+ return $this->_queryProfiles[$queryId];
+ }
+
+ /**
+ * Get an array of query profiles (ProfilerQuery objects). If $queryType
+ * is set to one of the Zend_Db_Profiler::* constants then only queries of that
+ * type will be returned. Normally, queries that have not yet ended will
+ * not be returned unless $showUnfinished is set to True. If no
+ * queries were found, False is returned. The returned array is indexed by the query
+ * profile handles.
+ *
+ * @param integer $queryType
+ * @param boolean $showUnfinished
+ * @return array|false
+ */
+ public function getQueryProfiles($queryType = null, $showUnfinished = false)
+ {
+ $queryProfiles = array();
+ foreach ($this->_queryProfiles as $key => $qp) {
+ if ($queryType === null) {
+ $condition = true;
+ } else {
+ $condition = ($qp->getQueryType() & $queryType);
+ }
+
+ if (($qp->hasEnded() || $showUnfinished) && $condition) {
+ $queryProfiles[$key] = $qp;
+ }
+ }
+
+ if (empty($queryProfiles)) {
+ $queryProfiles = false;
+ }
+
+ return $queryProfiles;
+ }
+
+ /**
+ * Get the total elapsed time (in seconds) of all of the profiled queries.
+ * Only queries that have ended will be counted. If $queryType is set to
+ * one or more of the Zend_Db_Profiler::* constants, the elapsed time will be calculated
+ * only for queries of the given type(s).
+ *
+ * @param integer $queryType OPTIONAL
+ * @return float
+ */
+ public function getTotalElapsedSecs($queryType = null)
+ {
+ $elapsedSecs = 0;
+ foreach ($this->_queryProfiles as $key => $qp) {
+ if (null === $queryType) {
+ $condition = true;
+ } else {
+ $condition = ($qp->getQueryType() & $queryType);
+ }
+ if (($qp->hasEnded()) && $condition) {
+ $elapsedSecs += $qp->getElapsedSecs();
+ }
+ }
+ return $elapsedSecs;
+ }
+
+ /**
+ * Get the total number of queries that have been profiled. Only queries that have ended will
+ * be counted. If $queryType is set to one of the Zend_Db_Profiler::* constants, only queries of
+ * that type will be counted.
+ *
+ * @param integer $queryType OPTIONAL
+ * @return integer
+ */
+ public function getTotalNumQueries($queryType = null)
+ {
+ if (null === $queryType) {
+ return count($this->_queryProfiles);
+ }
+
+ $numQueries = 0;
+ foreach ($this->_queryProfiles as $qp) {
+ if ($qp->hasEnded() && ($qp->getQueryType() & $queryType)) {
+ $numQueries++;
+ }
+ }
+
+ return $numQueries;
+ }
+
+ /**
+ * Get the ProfilerQuery object for the last query that was run, regardless if it has
+ * ended or not. If the query has not ended, its end time will be null. If no queries have
+ * been profiled, false is returned.
+ *
+ * @return ProfilerQuery|false
+ */
+ public function getLastQueryProfile()
+ {
+ if (empty($this->_queryProfiles)) {
+ return false;
+ }
+
+ end($this->_queryProfiles);
+
+ return current($this->_queryProfiles);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Profiler/ProfilerException.php b/vendor/gipfl/zfdb/src/Profiler/ProfilerException.php
new file mode 100644
index 0000000..3722793
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Profiler/ProfilerException.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Profiler;
+
+use gipfl\ZfDb\Exception\DbException;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class ProfilerException extends DbException
+{
+}
diff --git a/vendor/gipfl/zfdb/src/Profiler/ProfilerQuery.php b/vendor/gipfl/zfdb/src/Profiler/ProfilerQuery.php
new file mode 100644
index 0000000..7f79396
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Profiler/ProfilerQuery.php
@@ -0,0 +1,206 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Profiler;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class ProfilerQuery
+{
+
+ /**
+ * SQL query string or user comment, set by $query argument in constructor.
+ *
+ * @var string
+ */
+ protected $_query = '';
+
+ /**
+ * One of the Zend_Db_Profiler constants for query type, set by $queryType argument in constructor.
+ *
+ * @var integer
+ */
+ protected $_queryType = 0;
+
+ /**
+ * Unix timestamp with microseconds when instantiated.
+ *
+ * @var float
+ */
+ protected $_startedMicrotime = null;
+
+ /**
+ * Unix timestamp with microseconds when self::queryEnd() was called.
+ *
+ * @var integer
+ */
+ protected $_endedMicrotime = null;
+
+ /**
+ * @var array
+ */
+ protected $_boundParams = array();
+
+ /**
+ * @var array
+ */
+
+ /**
+ * Class constructor. A query is about to be started, save the query text ($query) and its
+ * type (one of the Zend_Db_Profiler::* constants).
+ *
+ * @param string $query
+ * @param integer $queryType
+ * @return void
+ */
+ public function __construct($query, $queryType)
+ {
+ $this->_query = $query;
+ $this->_queryType = $queryType;
+ // by default, and for backward-compatibility, start the click ticking
+ $this->start();
+ }
+
+ /**
+ * Clone handler for the query object.
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->_boundParams = array();
+ $this->_endedMicrotime = null;
+ $this->start();
+ }
+
+ /**
+ * Starts the elapsed time click ticking.
+ * This can be called subsequent to object creation,
+ * to restart the clock. For instance, this is useful
+ * right before executing a prepared query.
+ *
+ * @return void
+ */
+ public function start()
+ {
+ $this->_startedMicrotime = microtime(true);
+ }
+
+ /**
+ * Ends the query and records the time so that the elapsed time can be determined later.
+ *
+ * @return void
+ */
+ public function end()
+ {
+ $this->_endedMicrotime = microtime(true);
+ }
+
+ /**
+ * Returns true if and only if the query has ended.
+ *
+ * @return boolean
+ */
+ public function hasEnded()
+ {
+ return $this->_endedMicrotime !== null;
+ }
+
+ /**
+ * Get the original SQL text of the query.
+ *
+ * @return string
+ */
+ public function getQuery()
+ {
+ return $this->_query;
+ }
+
+ /**
+ * Get the type of this query (one of the Zend_Db_Profiler::* constants)
+ *
+ * @return integer
+ */
+ public function getQueryType()
+ {
+ return $this->_queryType;
+ }
+
+ /**
+ * @param string $param
+ * @param mixed $variable
+ * @return void
+ */
+ public function bindParam($param, $variable)
+ {
+ $this->_boundParams[$param] = $variable;
+ }
+
+ /**
+ * @param array $param
+ * @return void
+ */
+ public function bindParams(array $params)
+ {
+ if (array_key_exists(0, $params)) {
+ array_unshift($params, null);
+ unset($params[0]);
+ }
+ foreach ($params as $param => $value) {
+ $this->bindParam($param, $value);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueryParams()
+ {
+ return $this->_boundParams;
+ }
+
+ /**
+ * Get the elapsed time (in seconds) that the query ran.
+ * If the query has not yet ended, false is returned.
+ *
+ * @return float|false
+ */
+ public function getElapsedSecs()
+ {
+ if (null === $this->_endedMicrotime) {
+ return false;
+ }
+
+ return $this->_endedMicrotime - $this->_startedMicrotime;
+ }
+
+ /**
+ * Get the time (in seconds) when the profiler started running.
+ *
+ * @return bool|float
+ */
+ public function getStartedMicrotime()
+ {
+ if (null === $this->_startedMicrotime) {
+ return false;
+ }
+
+ return $this->_startedMicrotime;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Select.php b/vendor/gipfl/zfdb/src/Select.php
new file mode 100644
index 0000000..3a66e86
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Select.php
@@ -0,0 +1,1350 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb;
+
+use gipfl\ZfDb\Adapter\Adapter;
+use gipfl\ZfDb\Exception\DbException;
+use gipfl\ZfDb\Exception\SelectException;
+use PDOStatement;
+
+/**
+ * Class for SQL SELECT generation and results.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Select
+{
+ const DISTINCT = 'distinct';
+ const COLUMNS = 'columns';
+ const FROM = 'from';
+ const UNION = 'union';
+ const WHERE = 'where';
+ const GROUP = 'group';
+ const HAVING = 'having';
+ const ORDER = 'order';
+ const LIMIT_COUNT = 'limitcount';
+ const LIMIT_OFFSET = 'limitoffset';
+ const FOR_UPDATE = 'forupdate';
+
+ const INNER_JOIN = 'inner join';
+ const LEFT_JOIN = 'left join';
+ const RIGHT_JOIN = 'right join';
+ const FULL_JOIN = 'full join';
+ const CROSS_JOIN = 'cross join';
+ const NATURAL_JOIN = 'natural join';
+
+ const SQL_WILDCARD = '*';
+ const SQL_SELECT = 'SELECT';
+ const SQL_UNION = 'UNION';
+ const SQL_UNION_ALL = 'UNION ALL';
+ const SQL_FROM = 'FROM';
+ const SQL_WHERE = 'WHERE';
+ const SQL_DISTINCT = 'DISTINCT';
+ const SQL_GROUP_BY = 'GROUP BY';
+ const SQL_ORDER_BY = 'ORDER BY';
+ const SQL_HAVING = 'HAVING';
+ const SQL_FOR_UPDATE = 'FOR UPDATE';
+ const SQL_AND = 'AND';
+ const SQL_AS = 'AS';
+ const SQL_OR = 'OR';
+ const SQL_ON = 'ON';
+ const SQL_ASC = 'ASC';
+ const SQL_DESC = 'DESC';
+
+ const REGEX_COLUMN_EXPR = '/^([\w]*\s*\(([^\(\)]|(?1))*\))$/';
+ const REGEX_COLUMN_EXPR_ORDER = '/^([\w]+\s*\(([^\(\)]|(?1))*\))$/';
+ const REGEX_COLUMN_EXPR_GROUP = '/^([\w]+\s*\(([^\(\)]|(?1))*\))$/';
+
+ // @see http://stackoverflow.com/a/13823184/2028814
+ const REGEX_SQL_COMMENTS = '@
+ (([\'"]).*?[^\\\]\2) # $1 : Skip single & double quoted expressions
+ |( # $3 : Match comments
+ (?:\#|--).*?$ # - Single line comments
+ | # - Multi line (nested) comments
+ /\* # . comment open marker
+ (?: [^/*] # . non comment-marker characters
+ |/(?!\*) # . ! not a comment open
+ |\*(?!/) # . ! not a comment close
+ |(?R) # . recursive case
+ )* # . repeat eventually
+ \*\/ # . comment close marker
+ )\s* # Trim after comments
+ |(?<=;)\s+ # Trim after semi-colon
+ @msx';
+
+ /**
+ * Bind variables for query
+ *
+ * @var array
+ */
+ protected $_bind = array();
+
+ /**
+ * Zend_Db_Adapter_Abstract object.
+ *
+ * @var Adapter
+ */
+ protected $_adapter;
+
+ /**
+ * The initial values for the $_parts array.
+ * NOTE: It is important for the 'FOR_UPDATE' part to be last to ensure
+ * meximum compatibility with database adapters.
+ *
+ * @var array
+ */
+ protected static $_partsInit = array(
+ self::DISTINCT => false,
+ self::COLUMNS => array(),
+ self::UNION => array(),
+ self::FROM => array(),
+ self::WHERE => array(),
+ self::GROUP => array(),
+ self::HAVING => array(),
+ self::ORDER => array(),
+ self::LIMIT_COUNT => null,
+ self::LIMIT_OFFSET => null,
+ self::FOR_UPDATE => false
+ );
+
+ /**
+ * Specify legal join types.
+ *
+ * @var array
+ */
+ protected static $_joinTypes = array(
+ self::INNER_JOIN,
+ self::LEFT_JOIN,
+ self::RIGHT_JOIN,
+ self::FULL_JOIN,
+ self::CROSS_JOIN,
+ self::NATURAL_JOIN,
+ );
+
+ /**
+ * Specify legal union types.
+ *
+ * @var array
+ */
+ protected static $_unionTypes = array(
+ self::SQL_UNION,
+ self::SQL_UNION_ALL
+ );
+
+ /**
+ * The component parts of a SELECT statement.
+ * Initialized to the $_partsInit array in the constructor.
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * Tracks which columns are being select from each table and join.
+ *
+ * @var array
+ */
+ protected $_tableCols = array();
+
+ /**
+ * Class constructor
+ *
+ * @param Adapter $adapter
+ */
+ public function __construct(Adapter $adapter)
+ {
+ $this->_adapter = $adapter;
+ $this->_parts = self::$_partsInit;
+ }
+
+ /**
+ * Get bind variables
+ *
+ * @return array
+ */
+ public function getBind()
+ {
+ return $this->_bind;
+ }
+
+ /**
+ * Set bind variables
+ *
+ * @param mixed $bind
+ * @return Select
+ */
+ public function bind($bind)
+ {
+ $this->_bind = $bind;
+
+ return $this;
+ }
+
+ /**
+ * Makes the query SELECT DISTINCT.
+ *
+ * @param bool $flag Whether or not the SELECT is DISTINCT (default true).
+ * @return Select This Zend_Db_Select object.
+ */
+ public function distinct($flag = true)
+ {
+ $this->_parts[self::DISTINCT] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Adds a FROM table and optional columns to the query.
+ *
+ * The first parameter $name can be a simple string, in which case the
+ * correlation name is generated automatically. If you want to specify
+ * the correlation name, the first parameter must be an associative
+ * array in which the key is the correlation name, and the value is
+ * the physical table name. For example, array('alias' => 'table').
+ * The correlation name is prepended to all columns fetched for this
+ * table.
+ *
+ * The second parameter can be a single string or Zend_Db_Expr object,
+ * or else an array of strings or Zend_Db_Expr objects.
+ *
+ * The first parameter can be null or an empty string, in which case
+ * no correlation name is generated or prepended to the columns named
+ * in the second parameter.
+ *
+ * @param array|string|Expr $name The table name or an associative array
+ * relating correlation name to table name.
+ * @param array|string|Expr $cols The columns to select from this table.
+ * @param string $schema The schema name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function from($name, $cols = '*', $schema = null)
+ {
+ return $this->_join(self::FROM, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Specifies the columns used in the FROM clause.
+ *
+ * The parameter can be a single string or Zend_Db_Expr object,
+ * or else an array of strings or Zend_Db_Expr objects.
+ *
+ * @param array|string|Expr $cols The columns to select from this table.
+ * @param string $correlationName Correlation name of target table. OPTIONAL
+ * @return Select This Zend_Db_Select object.
+ */
+ public function columns($cols = '*', $correlationName = null)
+ {
+ if ($correlationName === null && count($this->_parts[self::FROM])) {
+ $correlationNameKeys = array_keys($this->_parts[self::FROM]);
+ $correlationName = current($correlationNameKeys);
+ }
+
+ if (!array_key_exists($correlationName, $this->_parts[self::FROM])) {
+ throw new SelectException("No table has been specified for the FROM clause");
+ }
+
+ $this->_tableCols($correlationName, $cols);
+
+ return $this;
+ }
+
+ /**
+ * Adds a UNION clause to the query.
+ *
+ * The first parameter has to be an array of Zend_Db_Select or
+ * sql query strings.
+ *
+ * <code>
+ * $sql1 = $db->select();
+ * $sql2 = "SELECT ...";
+ * $select = $db->select()
+ * ->union(array($sql1, $sql2))
+ * ->order("id");
+ * </code>
+ *
+ * @param array $select Array of select clauses for the union.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function union($select = array(), $type = self::SQL_UNION)
+ {
+ if (!is_array($select)) {
+ throw new SelectException(
+ "union() only accepts an array of Zend_Db_Select instances of sql query strings."
+ );
+ }
+
+ if (!in_array($type, self::$_unionTypes)) {
+ throw new SelectException("Invalid union type '{$type}'");
+ }
+
+ foreach ($select as $target) {
+ $this->_parts[self::UNION][] = array($target, $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a JOIN table and columns to the query.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function join($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->joinInner($name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add an INNER JOIN table and colums to the query
+ * Rows in both tables are matched according to the expression
+ * in the $cond argument. The result set is comprised
+ * of all cases where rows from the left table match
+ * rows from the right table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function joinInner($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::INNER_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a LEFT OUTER JOIN table and colums to the query
+ * All rows from the left operand table are included,
+ * matching rows from the right operand table included,
+ * and the columns from the right operand table are filled
+ * with NULLs if no row exists matching the left table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function joinLeft($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::LEFT_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a RIGHT OUTER JOIN table and colums to the query.
+ * Right outer join is the complement of left outer join.
+ * All rows from the right operand table are included,
+ * matching rows from the left operand table included,
+ * and the columns from the left operand table are filled
+ * with NULLs if no row exists matching the right table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function joinRight($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::RIGHT_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a FULL OUTER JOIN table and colums to the query.
+ * A full outer join is like combining a left outer join
+ * and a right outer join. All rows from both tables are
+ * included, paired with each other on the same row of the
+ * result set if they satisfy the join condition, and otherwise
+ * paired with NULLs in place of columns from the other table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function joinFull($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::FULL_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a CROSS JOIN table and colums to the query.
+ * A cross join is a cartesian product; there is no join condition.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function joinCross($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::CROSS_JOIN, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Add a NATURAL JOIN table and colums to the query.
+ * A natural join assumes an equi-join across any column(s)
+ * that appear with the same name in both tables.
+ * Only natural inner joins are supported by this API,
+ * even though SQL permits natural outer joins as well.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Expr $name The table name.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function joinNatural($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::NATURAL_JOIN, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Adds a WHERE condition to the query by AND.
+ *
+ * If a value is passed as the second param, it will be quoted
+ * and replaced into the condition wherever a question-mark
+ * appears. Array values are quoted and comma-separated.
+ *
+ * <code>
+ * // simplest but non-secure
+ * $select->where("id = $id");
+ *
+ * // secure (ID is quoted but matched anyway)
+ * $select->where('id = ?', $id);
+ *
+ * // alternatively, with named binding
+ * $select->where('id = :id');
+ * </code>
+ *
+ * Note that it is more correct to use named bindings in your
+ * queries for values other than strings. When you use named
+ * bindings, don't forget to pass the values when actually
+ * making a query:
+ *
+ * <code>
+ * $db->fetchAll($select, array('id' => 5));
+ * </code>
+ *
+ * @param string $cond The WHERE condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Select This Zend_Db_Select object.
+ */
+ public function where($cond, $value = null, $type = null)
+ {
+ $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true);
+
+ return $this;
+ }
+
+ /**
+ * Adds a WHERE condition to the query by OR.
+ *
+ * Otherwise identical to where().
+ *
+ * @param string $cond The WHERE condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Select This Zend_Db_Select object.
+ *
+ * @see where()
+ */
+ public function orWhere($cond, $value = null, $type = null)
+ {
+ $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, false);
+
+ return $this;
+ }
+
+ /**
+ * Adds grouping to the query.
+ *
+ * @param array|string $spec The column(s) to group by.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function group($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = array($spec);
+ }
+
+ foreach ($spec as $val) {
+ // Remove comments from SQL statement
+ $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val);
+ if (preg_match(self::REGEX_COLUMN_EXPR_GROUP, $noComments)) {
+ $val = new Expr($val);
+ }
+ $this->_parts[self::GROUP][] = $val;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a HAVING condition to the query by AND.
+ *
+ * If a value is passed as the second param, it will be quoted
+ * and replaced into the condition wherever a question-mark
+ * appears. See {@link where()} for an example
+ *
+ * @param string $cond The HAVING condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Select This Zend_Db_Select object.
+ */
+ public function having($cond, $value = null, $type = null)
+ {
+ if ($value !== null) {
+ $cond = $this->_adapter->quoteInto($cond, $value, $type);
+ }
+
+ if ($this->_parts[self::HAVING]) {
+ $this->_parts[self::HAVING][] = self::SQL_AND . " ($cond)";
+ } else {
+ $this->_parts[self::HAVING][] = "($cond)";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a HAVING condition to the query by OR.
+ *
+ * Otherwise identical to orHaving().
+ *
+ * @param string $cond The HAVING condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Select This Zend_Db_Select object.
+ *
+ * @see having()
+ */
+ public function orHaving($cond, $value = null, $type = null)
+ {
+ if ($value !== null) {
+ $cond = $this->_adapter->quoteInto($cond, $value, $type);
+ }
+
+ if ($this->_parts[self::HAVING]) {
+ $this->_parts[self::HAVING][] = self::SQL_OR . " ($cond)";
+ } else {
+ $this->_parts[self::HAVING][] = "($cond)";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a row order to the query.
+ *
+ * @param mixed $spec The column(s) and direction to order by.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function order($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = array($spec);
+ }
+
+ // force 'ASC' or 'DESC' on each order spec, default is ASC.
+ foreach ($spec as $val) {
+ if ($val instanceof Expr) {
+ $expr = $val->__toString();
+ if (empty($expr)) {
+ continue;
+ }
+ $this->_parts[self::ORDER][] = $val;
+ } else {
+ if (empty($val)) {
+ continue;
+ }
+ $direction = self::SQL_ASC;
+ if (preg_match('/(.*\W)(' . self::SQL_ASC . '|' . self::SQL_DESC . ')\b/si', $val, $matches)) {
+ $val = trim($matches[1]);
+ $direction = $matches[2];
+ }
+ // Remove comments from SQL statement
+ $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val);
+ if (preg_match(self::REGEX_COLUMN_EXPR_ORDER, $noComments)) {
+ $val = new Expr($val);
+ }
+ $this->_parts[self::ORDER][] = array($val, $direction);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a limit count and offset to the query.
+ *
+ * @param int $count OPTIONAL The number of rows to return.
+ * @param int $offset OPTIONAL Start returning after this many rows.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->_parts[self::LIMIT_COUNT] = (int) $count;
+ $this->_parts[self::LIMIT_OFFSET] = (int) $offset;
+ return $this;
+ }
+
+ /**
+ * Sets the limit and count by page number.
+ *
+ * @param int $page Limit results to this page number.
+ * @param int $rowCount Use this many rows per page.
+ * @return Select This Zend_Db_Select object.
+ */
+ public function limitPage($page, $rowCount)
+ {
+ $page = ($page > 0) ? $page : 1;
+ $rowCount = ($rowCount > 0) ? $rowCount : 1;
+ $this->_parts[self::LIMIT_COUNT] = (int) $rowCount;
+ $this->_parts[self::LIMIT_OFFSET] = (int) $rowCount * ($page - 1);
+ return $this;
+ }
+
+ /**
+ * Makes the query SELECT FOR UPDATE.
+ *
+ * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true).
+ * @return Select This Zend_Db_Select object.
+ */
+ public function forUpdate($flag = true)
+ {
+ $this->_parts[self::FOR_UPDATE] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get part of the structured information for the current query.
+ *
+ * @param string $part
+ * @return mixed
+ * @throws SelectException
+ */
+ public function getPart($part)
+ {
+ $part = strtolower($part);
+ if (!array_key_exists($part, $this->_parts)) {
+ throw new SelectException("Invalid Select part '$part'");
+ }
+ return $this->_parts[$part];
+ }
+
+ /**
+ * Executes the current select object and returns the result
+ *
+ * @param integer $fetchMode OPTIONAL
+ * @param mixed $bind An array of data to bind to the placeholders.
+ * @return PDOStatement|Statement
+ */
+ public function query($fetchMode = null, $bind = array())
+ {
+ if (!empty($bind)) {
+ $this->bind($bind);
+ }
+
+ $stmt = $this->_adapter->query($this);
+ if ($fetchMode == null) {
+ $fetchMode = $this->_adapter->getFetchMode();
+ }
+ $stmt->setFetchMode($fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Converts this object to an SQL SELECT string.
+ *
+ * @return string|null This object as a SELECT string. (or null if a string cannot be produced.)
+ * @throws SelectException
+ */
+ public function assemble()
+ {
+ $sql = self::SQL_SELECT;
+ foreach (array_keys(self::$_partsInit) as $part) {
+ $method = '_render' . ucfirst($part);
+ if (method_exists($this, $method)) {
+ $sql = $this->$method($sql);
+ }
+ }
+ return $sql;
+ }
+
+ /**
+ * Clear parts of the Select object, or an individual part.
+ *
+ * @param string $part OPTIONAL
+ * @return Select
+ */
+ public function reset($part = null)
+ {
+ if ($part == null) {
+ $this->_parts = self::$_partsInit;
+ } elseif (array_key_exists($part, self::$_partsInit)) {
+ $this->_parts[$part] = self::$_partsInit[$part];
+ }
+ return $this;
+ }
+
+ /**
+ * Gets the Zend_Db_Adapter_Abstract for this
+ * particular Zend_Db_Select object.
+ *
+ * @return Adapter
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Populate the {@link $_parts} 'join' key
+ *
+ * Does the dirty work of populating the join key.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param null|string $type Type of join; inner, left, and null are currently supported
+ * @param array|string|Expr $name Table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any.
+ * @return Select This Zend_Db_Select object
+ * @throws SelectException
+ */
+ protected function _join($type, $name, $cond, $cols, $schema = null)
+ {
+ if (!in_array($type, self::$_joinTypes) && $type != self::FROM) {
+ throw new SelectException("Invalid join type '$type'");
+ }
+
+ if (count($this->_parts[self::UNION])) {
+ throw new SelectException("Invalid use of table with " . self::SQL_UNION);
+ }
+
+ if (empty($name)) {
+ $correlationName = $tableName = '';
+ } elseif (is_array($name)) {
+ // Must be array($correlationName => $tableName) or array($ident, ...)
+ foreach ($name as $_correlationName => $_tableName) {
+ if (is_string($_correlationName)) {
+ // We assume the key is the correlation name and value is the table name
+ $tableName = $_tableName;
+ $correlationName = $_correlationName;
+ } else {
+ // We assume just an array of identifiers, with no correlation name
+ $tableName = $_tableName;
+ $correlationName = $this->_uniqueCorrelation($tableName);
+ }
+ break;
+ }
+ } elseif ($name instanceof Expr|| $name instanceof Select) {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation('t');
+ } elseif (preg_match('/^(.+)\s+AS\s+(.+)$/i', $name, $m)) {
+ $tableName = $m[1];
+ $correlationName = $m[2];
+ } else {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation($tableName);
+ }
+
+ // Schema from table name overrides schema argument
+ if (!is_object($tableName) && false !== strpos($tableName, '.')) {
+ list($schema, $tableName) = explode('.', $tableName);
+ }
+
+ $lastFromCorrelationName = null;
+ if (!empty($correlationName)) {
+ if (array_key_exists($correlationName, $this->_parts[self::FROM])) {
+ /**
+ * @see SelectException
+ */
+ throw new SelectException("You cannot define a correlation name '$correlationName' more than once");
+ }
+
+ if ($type == self::FROM) {
+ // append this from after the last from joinType
+ $tmpFromParts = $this->_parts[self::FROM];
+ $this->_parts[self::FROM] = array();
+ // move all the froms onto the stack
+ while ($tmpFromParts) {
+ $currentCorrelationName = key($tmpFromParts);
+ if ($tmpFromParts[$currentCorrelationName]['joinType'] != self::FROM) {
+ break;
+ }
+ $lastFromCorrelationName = $currentCorrelationName;
+ $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts);
+ }
+ } else {
+ $tmpFromParts = array();
+ }
+ $this->_parts[self::FROM][$correlationName] = array(
+ 'joinType' => $type,
+ 'schema' => $schema,
+ 'tableName' => $tableName,
+ 'joinCondition' => $cond
+ );
+ while ($tmpFromParts) {
+ $currentCorrelationName = key($tmpFromParts);
+ $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts);
+ }
+ }
+
+ // add to the columns from this joined table
+ if ($type == self::FROM && $lastFromCorrelationName == null) {
+ $lastFromCorrelationName = true;
+ }
+ $this->_tableCols($correlationName, $cols, $lastFromCorrelationName);
+
+ return $this;
+ }
+
+ /**
+ * Handle JOIN... USING... syntax
+ *
+ * This is functionality identical to the existing JOIN methods, however
+ * the join condition can be passed as a single column name. This method
+ * then completes the ON condition by using the same field for the FROM
+ * table and the JOIN table.
+ *
+ * <code>
+ * $select = $db->select()->from('table1')
+ * ->joinUsing('table2', 'column1');
+ *
+ * // SELECT * FROM table1 JOIN table2 ON table1.column1 = table2.column2
+ * </code>
+ *
+ * These joins are called by the developer simply by adding 'Using' to the
+ * method name. E.g.
+ * * joinUsing
+ * * joinInnerUsing
+ * * joinFullUsing
+ * * joinRightUsing
+ * * joinLeftUsing
+ *
+ * @return Select This Zend_Db_Select object.
+ */
+ public function _joinUsing($type, $name, $cond, $cols = '*', $schema = null)
+ {
+ if (empty($this->_parts[self::FROM])) {
+ throw new SelectException("You can only perform a joinUsing after specifying a FROM table");
+ }
+
+ $join = $this->_adapter->quoteIdentifier(key($this->_parts[self::FROM]), true);
+ $from = $this->_adapter->quoteIdentifier($this->_uniqueCorrelation($name), true);
+
+ $joinCond = array();
+ foreach ((array)$cond as $fieldName) {
+ $cond1 = $from . '.' . $fieldName;
+ $cond2 = $join . '.' . $fieldName;
+ $joinCond[] = $cond1 . ' = ' . $cond2;
+ }
+ $cond = implode(' '.self::SQL_AND.' ', $joinCond);
+
+ return $this->_join($type, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Generate a unique correlation name
+ *
+ * @param string|array $name A qualified identifier.
+ * @return string A unique correlation name.
+ */
+ private function _uniqueCorrelation($name)
+ {
+ if (is_array($name)) {
+ $k = key($name);
+ $c = is_string($k) ? $k : end($name);
+ } else {
+ // Extract just the last name of a qualified table name
+ $dot = strrpos($name, '.');
+ $c = ($dot === false) ? $name : substr($name, $dot+1);
+ }
+ for ($i = 2; array_key_exists($c, $this->_parts[self::FROM]); ++$i) {
+ $c = $name . '_' . (string) $i;
+ }
+ return $c;
+ }
+
+ /**
+ * Adds to the internal table-to-column mapping array.
+ *
+ * @param string $tbl The table/join the columns come from.
+ * @param array|string $cols The list of columns; preferably as
+ * an array, but possibly as a string containing one column.
+ * @param bool|string True if it should be prepended, a correlation name if it should be inserted
+ * @return void
+ */
+ protected function _tableCols($correlationName, $cols, $afterCorrelationName = null)
+ {
+ if (!is_array($cols)) {
+ $cols = array($cols);
+ }
+
+ if ($correlationName == null) {
+ $correlationName = '';
+ }
+
+ $columnValues = array();
+
+ foreach (array_filter($cols) as $alias => $col) {
+ $currentCorrelationName = $correlationName;
+ if (is_string($col)) {
+ // Check for a column matching "<column> AS <alias>" and extract the alias name
+ $col = trim(str_replace("\n", ' ', $col));
+ if (preg_match('/^(.+)\s+' . self::SQL_AS . '\s+(.+)$/i', $col, $m)) {
+ $col = $m[1];
+ $alias = $m[2];
+ }
+ // Check for columns that look like functions and convert to Zend_Db_Expr
+ if (preg_match(self::REGEX_COLUMN_EXPR, (string) $col)) {
+ $col = new Expr($col);
+ } elseif (preg_match('/(.+)\.(.+)/', $col, $m)) {
+ $currentCorrelationName = $m[1];
+ $col = $m[2];
+ }
+ }
+ $columnValues[] = array($currentCorrelationName, $col, is_string($alias) ? $alias : null);
+ }
+
+ if ($columnValues) {
+ // should we attempt to prepend or insert these values?
+ if ($afterCorrelationName === true || is_string($afterCorrelationName)) {
+ $tmpColumns = $this->_parts[self::COLUMNS];
+ $this->_parts[self::COLUMNS] = array();
+ } else {
+ $tmpColumns = array();
+ }
+
+ // find the correlation name to insert after
+ if (is_string($afterCorrelationName)) {
+ while ($tmpColumns) {
+ $this->_parts[self::COLUMNS][] = $currentColumn = array_shift($tmpColumns);
+ if ($currentColumn[0] == $afterCorrelationName) {
+ break;
+ }
+ }
+ }
+
+ // apply current values to current stack
+ foreach ($columnValues as $columnValue) {
+ array_push($this->_parts[self::COLUMNS], $columnValue);
+ }
+
+ // finish ensuring that all previous values are applied (if they exist)
+ while ($tmpColumns) {
+ array_push($this->_parts[self::COLUMNS], array_shift($tmpColumns));
+ }
+ }
+ }
+
+ /**
+ * Internal function for creating the where clause
+ *
+ * @param string $condition
+ * @param mixed $value optional
+ * @param string $type optional
+ * @param boolean $bool true = AND, false = OR
+ * @return string clause
+ */
+ protected function _where($condition, $value = null, $type = null, $bool = true)
+ {
+ if (count($this->_parts[self::UNION])) {
+ throw new SelectException("Invalid use of where clause with " . self::SQL_UNION);
+ }
+
+ if ($value !== null) {
+ $condition = $this->_adapter->quoteInto($condition, $value, $type);
+ }
+
+ $cond = "";
+ if ($this->_parts[self::WHERE]) {
+ if ($bool === true) {
+ $cond = self::SQL_AND . ' ';
+ } else {
+ $cond = self::SQL_OR . ' ';
+ }
+ }
+
+ return $cond . "($condition)";
+ }
+
+ /**
+ * @return array
+ */
+ protected function _getDummyTable()
+ {
+ return array();
+ }
+
+ /**
+ * Return a quoted schema name
+ *
+ * @param string $schema The schema name OPTIONAL
+ * @return string|null
+ */
+ protected function _getQuotedSchema($schema = null)
+ {
+ if ($schema === null) {
+ return null;
+ }
+ return $this->_adapter->quoteIdentifier($schema, true) . '.';
+ }
+
+ /**
+ * Return a quoted table name
+ *
+ * @param string $tableName The table name
+ * @param string $correlationName The correlation name OPTIONAL
+ * @return string
+ */
+ protected function _getQuotedTable($tableName, $correlationName = null)
+ {
+ return $this->_adapter->quoteTableAs($tableName, $correlationName, true);
+ }
+
+ /**
+ * Render DISTINCT clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderDistinct($sql)
+ {
+ if ($this->_parts[self::DISTINCT]) {
+ $sql .= ' ' . self::SQL_DISTINCT;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render DISTINCT clause
+ *
+ * @param string $sql SQL query
+ * @return string|null
+ */
+ protected function _renderColumns($sql)
+ {
+ if (!count($this->_parts[self::COLUMNS])) {
+ return null;
+ }
+
+ $columns = array();
+ foreach ($this->_parts[self::COLUMNS] as $columnEntry) {
+ list($correlationName, $column, $alias) = $columnEntry;
+ if ($column instanceof Expr) {
+ $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true);
+ } else {
+ if ($column == self::SQL_WILDCARD) {
+ $column = new Expr(self::SQL_WILDCARD);
+ $alias = null;
+ }
+ if (empty($correlationName)) {
+ $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true);
+ } else {
+ $columns[] = $this->_adapter->quoteColumnAs(array($correlationName, $column), $alias, true);
+ }
+ }
+ }
+
+ return $sql . ' ' . implode(', ', $columns);
+ }
+
+ /**
+ * Render FROM clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderFrom($sql)
+ {
+ /*
+ * If no table specified, use RDBMS-dependent solution
+ * for table-less query. e.g. DUAL in Oracle.
+ */
+ if (empty($this->_parts[self::FROM])) {
+ $this->_parts[self::FROM] = $this->_getDummyTable();
+ }
+
+ $from = array();
+
+ foreach ($this->_parts[self::FROM] as $correlationName => $table) {
+ $tmp = '';
+
+ $joinType = ($table['joinType'] == self::FROM) ? self::INNER_JOIN : $table['joinType'];
+
+ // Add join clause (if applicable)
+ if (! empty($from)) {
+ $tmp .= ' ' . strtoupper($joinType) . ' ';
+ }
+
+ $tmp .= $this->_getQuotedSchema($table['schema']);
+ $tmp .= $this->_getQuotedTable($table['tableName'], $correlationName);
+
+ // Add join conditions (if applicable)
+ if (!empty($from) && ! empty($table['joinCondition'])) {
+ $tmp .= ' ' . self::SQL_ON . ' ' . $table['joinCondition'];
+ }
+
+ // Add the table name and condition add to the list
+ $from[] = $tmp;
+ }
+
+ // Add the list of all joins
+ if (!empty($from)) {
+ $sql .= ' ' . self::SQL_FROM . ' ' . implode("\n", $from);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render UNION query
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderUnion($sql)
+ {
+ if ($this->_parts[self::UNION]) {
+ $parts = count($this->_parts[self::UNION]);
+ foreach ($this->_parts[self::UNION] as $cnt => $union) {
+ list($target, $type) = $union;
+ if ($target instanceof Select) {
+ $target = $target->assemble();
+ }
+ $sql .= $target;
+ if ($cnt < $parts - 1) {
+ $sql .= ' ' . $type . ' ';
+ }
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render WHERE clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderWhere($sql)
+ {
+ if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) {
+ $sql .= ' ' . self::SQL_WHERE . ' ' . implode(' ', $this->_parts[self::WHERE]);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render GROUP clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderGroup($sql)
+ {
+ if ($this->_parts[self::FROM] && $this->_parts[self::GROUP]) {
+ $group = array();
+ foreach ($this->_parts[self::GROUP] as $term) {
+ $group[] = $this->_adapter->quoteIdentifier($term, true);
+ }
+ $sql .= ' ' . self::SQL_GROUP_BY . ' ' . implode(",\n\t", $group);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render HAVING clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderHaving($sql)
+ {
+ if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) {
+ $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render ORDER clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderOrder($sql)
+ {
+ if ($this->_parts[self::ORDER]) {
+ $order = array();
+ foreach ($this->_parts[self::ORDER] as $term) {
+ if (is_array($term)) {
+ if (is_numeric($term[0]) && strval(intval($term[0])) == $term[0]) {
+ $order[] = (int)trim($term[0]) . ' ' . $term[1];
+ } else {
+ $order[] = $this->_adapter->quoteIdentifier($term[0], true) . ' ' . $term[1];
+ }
+ } elseif (is_numeric($term) && strval(intval($term)) == $term) {
+ $order[] = (int)trim($term);
+ } else {
+ $order[] = $this->_adapter->quoteIdentifier($term, true);
+ }
+ }
+ $sql .= ' ' . self::SQL_ORDER_BY . ' ' . implode(', ', $order);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render LIMIT OFFSET clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderLimitoffset($sql)
+ {
+ $count = 0;
+ $offset = 0;
+
+ if (!empty($this->_parts[self::LIMIT_OFFSET])) {
+ $offset = (int) $this->_parts[self::LIMIT_OFFSET];
+ $count = PHP_INT_MAX;
+ }
+
+ if (!empty($this->_parts[self::LIMIT_COUNT])) {
+ $count = (int) $this->_parts[self::LIMIT_COUNT];
+ }
+
+ /*
+ * Add limits clause
+ */
+ if ($count > 0) {
+ $sql = trim($this->_adapter->limit($sql, $count, $offset));
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render FOR UPDATE clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderForupdate($sql)
+ {
+ if ($this->_parts[self::FOR_UPDATE]) {
+ $sql .= ' ' . self::SQL_FOR_UPDATE;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Turn magic function calls into non-magic function calls
+ * for joinUsing syntax
+ *
+ * @param string $method
+ * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
+ * @return Select
+ * @throws SelectException If an invalid method is called.
+ */
+ public function __call($method, array $args)
+ {
+ $matches = array();
+
+ /**
+ * Recognize methods for Has-Many cases:
+ * findParent<Class>()
+ * findParent<Class>By<Rule>()
+ * Use the non-greedy pattern repeat modifier e.g. \w+?
+ */
+ if (preg_match('/^join([a-zA-Z]*?)Using$/', $method, $matches)) {
+ $type = strtolower($matches[1]);
+ if ($type) {
+ $type .= ' join';
+ if (!in_array($type, self::$_joinTypes)) {
+ throw new SelectException("Unrecognized method '$method()'");
+ }
+ if (in_array($type, array(self::CROSS_JOIN, self::NATURAL_JOIN))) {
+ throw new SelectException("Cannot perform a joinUsing with method '$method()'");
+ }
+ } else {
+ $type = self::INNER_JOIN;
+ }
+ array_unshift($args, $type);
+ return call_user_func_array(array($this, '_joinUsing'), $args);
+ }
+
+ throw new SelectException("Unrecognized method '$method()'");
+ }
+
+ /**
+ * Implements magic method.
+ *
+ * @return string This object as a SELECT string.
+ */
+ public function __toString()
+ {
+ try {
+ $sql = $this->assemble();
+ } catch (DbException $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ $sql = '';
+ }
+ return (string)$sql;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement.php b/vendor/gipfl/zfdb/src/Statement.php
new file mode 100644
index 0000000..3288be6
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement.php
@@ -0,0 +1,462 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb;
+
+use gipfl\ZfDb\Adapter\Adapter;
+use gipfl\ZfDb\Profiler\ProfilerQuery;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+use gipfl\ZfDb\Statement\StatementInterface;
+
+/**
+ * Abstract class to emulate a PDOStatement for native database adapters.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Statement implements StatementInterface
+{
+ /**
+ * @var resource|object The driver level statement object/resource
+ */
+ protected $_stmt = null;
+
+ /**
+ * @var Adapter
+ */
+ protected $_adapter = null;
+
+ /**
+ * The current fetch mode.
+ *
+ * @var integer
+ */
+ protected $_fetchMode = Db::FETCH_ASSOC;
+
+ /**
+ * Attributes.
+ *
+ * @var array
+ */
+ protected $_attribute = array();
+
+ /**
+ * Column result bindings.
+ *
+ * @var array
+ */
+ protected $_bindColumn = array();
+
+ /**
+ * Query parameter bindings; covers bindParam() and bindValue().
+ *
+ * @var array
+ */
+ protected $_bindParam = array();
+
+ /**
+ * SQL string split into an array at placeholders.
+ *
+ * @var array
+ */
+ protected $_sqlSplit = array();
+
+ /**
+ * Parameter placeholders in the SQL string by position in the split array.
+ *
+ * @var array
+ */
+ protected $_sqlParam = array();
+
+ /**
+ * @var ProfilerQuery
+ */
+ protected $_queryId = null;
+
+ /**
+ * Constructor for a statement.
+ *
+ * @param Adapter $adapter
+ * @param mixed $sql Either a string or Zend_Db_Select.
+ */
+ public function __construct($adapter, $sql)
+ {
+ $this->_adapter = $adapter;
+ if ($sql instanceof Select) {
+ $sql = $sql->assemble();
+ }
+ $this->_parseParameters($sql);
+ $this->_prepare($sql);
+
+ $this->_queryId = $this->_adapter->getProfiler()->queryStart($sql);
+ }
+
+ /**
+ * Internal method called by abstract statment constructor to setup
+ * the driver level statement
+ *
+ * @return void
+ */
+ protected function _prepare($sql)
+ {
+ return;
+ }
+
+ /**
+ * @param string $sql
+ * @return void
+ */
+ protected function _parseParameters($sql)
+ {
+ $sql = $this->_stripQuoted($sql);
+
+ // split into text and params
+ $this->_sqlSplit = preg_split(
+ '/(\?|:[a-zA-Z0-9_]+)/',
+ $sql,
+ -1,
+ PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
+ );
+
+ // map params
+ $this->_sqlParam = array();
+ foreach ($this->_sqlSplit as $key => $val) {
+ if ($val == '?') {
+ if ($this->_adapter->supportsParameters('positional') === false) {
+ throw new StatementException("Invalid bind-variable position '$val'");
+ }
+ } elseif ($val[0] == ':') {
+ if ($this->_adapter->supportsParameters('named') === false) {
+ throw new StatementException("Invalid bind-variable name '$val'");
+ }
+ }
+ $this->_sqlParam[] = $val;
+ }
+
+ // set up for binding
+ $this->_bindParam = array();
+ }
+
+ /**
+ * Remove parts of a SQL string that contain quoted strings
+ * of values or identifiers.
+ *
+ * @param string $sql
+ * @return string
+ */
+ protected function _stripQuoted($sql)
+ {
+ // get the character for value quoting
+ // this should be '
+ $q = $this->_adapter->quote('a');
+ $q = $q[0];
+ // get the value used as an escaped quote,
+ // e.g. \' or ''
+ $qe = $this->_adapter->quote($q);
+ $qe = substr($qe, 1, 2);
+ $qe = preg_quote($qe);
+ $escapeChar = substr($qe, 0, 1);
+ // remove 'foo\'bar'
+ if (!empty($q)) {
+ $escapeChar = preg_quote($escapeChar);
+ // this segfaults only after 65,000 characters instead of 9,000
+ $sql = preg_replace("/$q([^$q{$escapeChar}]*|($qe)*)*$q/s", '', $sql);
+ }
+
+ // get a version of the SQL statement with all quoted
+ // values and delimited identifiers stripped out
+ // remove "foo\"bar"
+ $sql = preg_replace("/\"(\\\\\"|[^\"])*\"/Us", '', $sql);
+
+ // get the character for delimited id quotes,
+ // this is usually " but in MySQL is `
+ $d = $this->_adapter->quoteIdentifier('a');
+ $d = $d[0];
+ // get the value used as an escaped delimited id quote,
+ // e.g. \" or "" or \`
+ $de = $this->_adapter->quoteIdentifier($d);
+ $de = substr($de, 1, 2);
+ $de = preg_quote($de);
+ // Note: $de and $d where never used..., now they are:
+ $sql = preg_replace("/$d($de|\\\\{2}|[^$d])*$d/Us", '', $sql);
+ return $sql;
+ }
+
+ /**
+ * Bind a column of the statement result set to a PHP variable.
+ *
+ * @param string $column Name the column in the result set, either by
+ * position or by name.
+ * @param mixed $param Reference to the PHP variable containing the value.
+ * @param mixed $type OPTIONAL
+ * @return bool
+ */
+ public function bindColumn($column, &$param, $type = null)
+ {
+ $this->_bindColumn[$column] =& $param;
+ return true;
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ */
+ public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ if (!is_int($parameter) && !is_string($parameter)) {
+ throw new StatementException('Invalid bind-variable position');
+ }
+
+ $position = null;
+ if (($intval = (int) $parameter) > 0 && $this->_adapter->supportsParameters('positional')) {
+ if ($intval >= 1 || $intval <= count($this->_sqlParam)) {
+ $position = $intval;
+ }
+ } elseif ($this->_adapter->supportsParameters('named')) {
+ if ($parameter[0] != ':') {
+ $parameter = ':' . $parameter;
+ }
+ if (in_array($parameter, $this->_sqlParam) !== false) {
+ $position = $parameter;
+ }
+ }
+
+ if ($position === null) {
+ throw new StatementException("Invalid bind-variable position '$parameter'");
+ }
+
+ // Finally we are assured that $position is valid
+ $this->_bindParam[$position] =& $variable;
+ return $this->_bindParam($position, $variable, $type, $length, $options);
+ }
+
+ /**
+ * Binds a value to a parameter.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $value Scalar value to bind to the parameter.
+ * @param mixed $type OPTIONAL Datatype of the parameter.
+ * @return bool
+ */
+ public function bindValue($parameter, $value, $type = null)
+ {
+ return $this->bindParam($parameter, $value, $type);
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ */
+ public function execute(array $params = null)
+ {
+ /*
+ * Simple case - no query profiler to manage.
+ */
+ if ($this->_queryId === null) {
+ return $this->_execute($params);
+ }
+
+ /*
+ * Do the same thing, but with query profiler
+ * management before and after the execute.
+ */
+ $prof = $this->_adapter->getProfiler();
+ $qp = $prof->getQueryProfile($this->_queryId);
+ if ($qp->hasEnded()) {
+ $this->_queryId = $prof->queryClone($qp);
+ $qp = $prof->getQueryProfile($this->_queryId);
+ }
+ if ($params !== null) {
+ $qp->bindParams($params);
+ } else {
+ $qp->bindParams($this->_bindParam);
+ }
+ $qp->start($this->_queryId);
+
+ $retval = $this->_execute($params);
+
+ $prof->queryEnd($this->_queryId);
+
+ return $retval;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = array();
+ if ($style === Db::FETCH_COLUMN && $col === null) {
+ $col = 0;
+ }
+ if ($col === null) {
+ while ($row = $this->fetch($style)) {
+ $data[] = $row;
+ }
+ } else {
+ while (false !== ($val = $this->fetchColumn($col))) {
+ $data[] = $val;
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string One value from the next row of result set, or false.
+ */
+ public function fetchColumn($col = 0)
+ {
+ $data = array();
+ $col = (int) $col;
+ $row = $this->fetch(Db::FETCH_NUM);
+ if (!is_array($row)) {
+ return false;
+ }
+ return $row[$col];
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class, or false.
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ $obj = new $class($config);
+ $row = $this->fetch(Db::FETCH_ASSOC);
+ if (!is_array($row)) {
+ return false;
+ }
+ foreach ($row as $key => $val) {
+ $obj->$key = $val;
+ }
+ return $obj;
+ }
+
+ /**
+ * Retrieve a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @return mixed Attribute value.
+ */
+ public function getAttribute($key)
+ {
+ if (array_key_exists($key, $this->_attribute)) {
+ return $this->_attribute[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Set a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @param mixed $val Attribute value.
+ */
+ public function setAttribute($key, $val)
+ {
+ $this->_attribute[$key] = $val;
+ }
+
+ /**
+ * Set the default fetch mode for this statement.
+ *
+ * @param int $mode The fetch mode.
+ * @return bool
+ * @throws StatementException
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Db::FETCH_NUM:
+ case Db::FETCH_ASSOC:
+ case Db::FETCH_BOTH:
+ case Db::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ case Db::FETCH_BOUND:
+ default:
+ $this->closeCursor();
+ throw new StatementException('invalid fetch mode');
+ }
+
+ return true;
+ }
+
+ /**
+ * Helper function to map retrieved row
+ * to bound column variables
+ *
+ * @param array $row
+ * @return bool True
+ */
+ public function _fetchBound($row)
+ {
+ foreach ($row as $key => $value) {
+ // bindColumn() takes 1-based integer positions
+ // but fetch() returns 0-based integer indexes
+ if (is_int($key)) {
+ $key++;
+ }
+ // set results only to variables that were bound previously
+ if (isset($this->_bindColumn[$key])) {
+ $this->_bindColumn[$key] = $value;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets the Zend_Db_Adapter_Abstract for this
+ * particular Zend_Db_Statement object.
+ *
+ * @return Adapter
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Gets the resource or object setup by the
+ * _parse
+ * @return mixed
+ */
+ public function getDriverStatement()
+ {
+ return $this->_stmt;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Db2Statement.php b/vendor/gipfl/zfdb/src/Statement/Db2Statement.php
new file mode 100644
index 0000000..ebee20f
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Db2Statement.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement;
+
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Statement;
+use gipfl\ZfDb\Statement\Exception\StatementExceptionDb2;
+
+/**
+ * Extends for DB2 native adapter.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Db2Statement extends Statement
+{
+ /**
+ * Column names.
+ */
+ protected $_keys;
+
+ /**
+ * Fetched result values.
+ */
+ protected $_values;
+
+ /**
+ * Prepare a statement handle.
+ *
+ * @param string $sql
+ * @return void
+ * @throws StatementExceptionDb2
+ */
+ public function _prepare($sql)
+ {
+ $connection = $this->_adapter->getConnection();
+
+ // db2_prepare on i5 emits errors, these need to be
+ // suppressed so that proper exceptions can be thrown
+ $this->_stmt = @db2_prepare($connection, $sql);
+
+ if (!$this->_stmt) {
+ throw new StatementExceptionDb2(
+ db2_stmt_errormsg(),
+ db2_stmt_error()
+ );
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementExceptionDb2
+ */
+ public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ if ($type === null) {
+ $type = DB2_PARAM_IN;
+ }
+
+ if (isset($options['data-type'])) {
+ $datatype = $options['data-type'];
+ } else {
+ $datatype = DB2_CHAR;
+ }
+
+ if (!db2_bind_param($this->_stmt, $parameter, "variable", $type, $datatype)) {
+ throw new StatementExceptionDb2(
+ db2_stmt_errormsg(),
+ db2_stmt_error()
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ db2_free_stmt($this->_stmt);
+ $this->_stmt = false;
+ return true;
+ }
+
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return db2_num_fields($this->_stmt);
+ }
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = db2_stmt_error();
+ if ($error === '') {
+ return false;
+ }
+
+ return $error;
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array|false
+ */
+ public function errorInfo()
+ {
+ $error = $this->errorCode();
+ if ($error === false) {
+ return false;
+ }
+
+ /*
+ * Return three-valued array like PDO. But DB2 does not distinguish
+ * between SQLCODE and native RDBMS error code, so repeat the SQLCODE.
+ */
+ return array(
+ $error,
+ $error,
+ db2_stmt_errormsg()
+ );
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws StatementExceptionDb2
+ */
+ public function _execute(array $params = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $retval = true;
+ if ($params !== null) {
+ $retval = @db2_execute($this->_stmt, $params);
+ } else {
+ $retval = @db2_execute($this->_stmt);
+ }
+
+ if ($retval === false) {
+ throw new StatementExceptionDb2(
+ db2_stmt_errormsg(),
+ db2_stmt_error()
+ );
+ }
+
+ $this->_keys = array();
+ if ($field_num = $this->columnCount()) {
+ for ($i = 0; $i < $field_num; $i++) {
+ $name = db2_field_name($this->_stmt, $i);
+ $this->_keys[] = $name;
+ }
+ }
+
+ $this->_values = array();
+ if ($this->_keys) {
+ $this->_values = array_fill(0, count($this->_keys), null);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws StatementExceptionDb2
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ switch ($style) {
+ case Db::FETCH_NUM:
+ $row = db2_fetch_array($this->_stmt);
+ break;
+ case Db::FETCH_ASSOC:
+ $row = db2_fetch_assoc($this->_stmt);
+ break;
+ case Db::FETCH_BOTH:
+ $row = db2_fetch_both($this->_stmt);
+ break;
+ case Db::FETCH_OBJ:
+ $row = db2_fetch_object($this->_stmt);
+ break;
+ case Db::FETCH_BOUND:
+ $row = db2_fetch_both($this->_stmt);
+ if ($row !== false) {
+ return $this->_fetchBound($row);
+ }
+ break;
+ default:
+ throw new StatementExceptionDb2("Invalid fetch mode '$style' specified");
+ }
+
+ return $row;
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ $obj = $this->fetch(Db::FETCH_OBJ);
+ return $obj;
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws StatementExceptionDb2
+ */
+ public function nextRowset()
+ {
+ throw new StatementExceptionDb2(__FUNCTION__ . '() is not implemented');
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ */
+ public function rowCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $num = @db2_num_rows($this->_stmt);
+
+ if ($num === false) {
+ return 0;
+ }
+
+ return $num;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Exception/StatementException.php b/vendor/gipfl/zfdb/src/Statement/Exception/StatementException.php
new file mode 100644
index 0000000..48450fc
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Exception/StatementException.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Exception;
+
+use gipfl\ZfDb\Exception\DbException;
+
+/**
+ * Zend_Db_Statement_Exception
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class StatementException extends DbException
+{
+ /**
+ * Check if this general exception has a specific database driver specific exception nested inside.
+ *
+ * @return bool
+ */
+ public function hasChainedException()
+ {
+ return ($this->getPrevious() !== null);
+ }
+
+ /**
+ * @return \Exception|null
+ */
+ public function getChainedException()
+ {
+ return $this->getPrevious();
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionDb2.php b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionDb2.php
new file mode 100644
index 0000000..23de2d7
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionDb2.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Exception;
+
+use gipfl\ZfDb\Statement\Exception\StatementException;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class StatementExceptionDb2 extends StatementException
+{
+ /**
+ * @var string
+ */
+ protected $code = '00000';
+
+ /**
+ * @var string
+ */
+ protected $message = 'unknown exception';
+
+ /**
+ * @param string $msg
+ * @param string $state
+ */
+ public function __construct($msg = 'unknown exception', $state = '00000')
+ {
+ $this->message = $msg;
+ $this->code = $state;
+ parent::__construct($this->message, $this->code);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionMysqli.php b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionMysqli.php
new file mode 100644
index 0000000..9adbbc3
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionMysqli.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Exception;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class StatementExceptionMysqli extends StatementException
+{
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionOracle.php b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionOracle.php
new file mode 100644
index 0000000..64e0de7
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionOracle.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Exception;
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class StatementExceptionOracle extends StatementException
+{
+ protected $message = 'Unknown exception';
+ protected $code = 0;
+
+ public function __construct($error = null, $code = 0)
+ {
+ if (is_array($error)) {
+ if (!isset($error['offset'])) {
+ $this->message = $error['code']." ".$error['message'];
+ } else {
+ $this->message = $error['code']." ".$error['message']." ";
+ $this->message .= substr($error['sqltext'], 0, $error['offset']);
+ $this->message .= "*";
+ $this->message .= substr($error['sqltext'], $error['offset']);
+ }
+ $this->code = $error['code'];
+ }
+ if (!$this->code && $code) {
+ $this->code = $code;
+ }
+ parent::__construct($this->message, $this->code);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionSqlsrv.php b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionSqlsrv.php
new file mode 100644
index 0000000..a2bcd16
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Exception/StatementExceptionSqlsrv.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Exception;
+
+/**
+ * @see StatementException
+ */
+
+/**
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class StatementExceptionSqlsrv extends StatementException
+{
+ /**
+ * Constructor
+ *
+ * If $message is an array, the assumption is that the return value of
+ * sqlsrv_errors() was provided. If so, it then retrieves the most recent
+ * error from that stack, and sets the message and code based on it.
+ *
+ * @param null|array|string $message
+ * @param null|int $code
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ if (is_array($message)) {
+ // Error should be array of errors
+ // We only need first one (?)
+ if (isset($message[0])) {
+ $message = $message[0];
+ }
+
+ $code = (int) $message['code'];
+ $message = (string) $message['message'];
+ }
+ parent::__construct($message, $code);
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/MysqliStatement.php b/vendor/gipfl/zfdb/src/Statement/MysqliStatement.php
new file mode 100644
index 0000000..4aa62d2
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/MysqliStatement.php
@@ -0,0 +1,345 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement;
+
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Statement;
+use gipfl\ZfDb\Statement\Exception\StatementExceptionMysqli;
+
+/**
+ * Extends for Mysqli
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class MysqliStatement extends Statement
+{
+ /**
+ * Column names.
+ *
+ * @var array
+ */
+ protected $_keys;
+
+ /**
+ * Fetched result values.
+ *
+ * @var array
+ */
+ protected $_values;
+
+ /**
+ * @var array
+ */
+ protected $_meta = null;
+
+ /**
+ * @param string $sql
+ * @return void
+ * @throws StatementExceptionMysqli
+ */
+ public function _prepare($sql)
+ {
+ $mysqli = $this->_adapter->getConnection();
+
+ $this->_stmt = $mysqli->prepare($sql);
+
+ if ($this->_stmt === false || $mysqli->errno) {
+ /**
+ * @see StatementExceptionMysqli
+ */
+ throw new StatementExceptionMysqli("Mysqli prepare error: " . $mysqli->error, $mysqli->errno);
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementExceptionMysqli
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ return true;
+ }
+
+ /**
+ * Closes the cursor and the statement.
+ *
+ * @return bool
+ */
+ public function close()
+ {
+ if ($this->_stmt) {
+ $r = $this->_stmt->close();
+ $this->_stmt = null;
+ return $r;
+ }
+ return false;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if ($stmt = $this->_stmt) {
+ $mysqli = $this->_adapter->getConnection();
+ while ($mysqli->more_results()) {
+ $mysqli->next_result();
+ }
+ $this->_stmt->free_result();
+ return $this->_stmt->reset();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if (isset($this->_meta) && $this->_meta) {
+ return $this->_meta->field_count;
+ }
+ return 0;
+ }
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return substr($this->_stmt->sqlstate, 0, 5);
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array|bool
+ */
+ public function errorInfo()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return array(
+ substr($this->_stmt->sqlstate, 0, 5),
+ $this->_stmt->errno,
+ $this->_stmt->error,
+ );
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws StatementExceptionMysqli
+ */
+ public function _execute(array $params = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ // if no params were given as an argument to execute(),
+ // then default to the _bindParam array
+ if ($params === null) {
+ $params = $this->_bindParam;
+ }
+ // send $params as input parameters to the statement
+ if ($params) {
+ array_unshift($params, str_repeat('s', count($params)));
+ $stmtParams = array();
+ foreach ($params as $k => &$value) {
+ $stmtParams[$k] = &$value;
+ }
+ call_user_func_array(
+ array($this->_stmt, 'bind_param'),
+ $stmtParams
+ );
+ }
+
+ // execute the statement
+ $retval = $this->_stmt->execute();
+ if ($retval === false) {
+ throw new StatementExceptionMysqli(
+ "Mysqli statement execute error : " . $this->_stmt->error,
+ $this->_stmt->errno
+ );
+ }
+
+
+ // retain metadata
+ if ($this->_meta === null) {
+ $this->_meta = $this->_stmt->result_metadata();
+ if ($this->_stmt->errno) {
+ /**
+ * @see StatementExceptionMysqli
+ */
+ throw new StatementExceptionMysqli(
+ "Mysqli statement metadata error: " . $this->_stmt->error,
+ $this->_stmt->errno
+ );
+ }
+ }
+
+ // statements that have no result set do not return metadata
+ if ($this->_meta !== false) {
+ // get the column names that will result
+ $this->_keys = array();
+ foreach ($this->_meta->fetch_fields() as $col) {
+ $this->_keys[] = $this->_adapter->foldCase($col->name);
+ }
+
+ // set up a binding space for result variables
+ $this->_values = array_fill(0, count($this->_keys), null);
+
+ // set up references to the result binding space.
+ // just passing $this->_values in the call_user_func_array()
+ // below won't work, you need references.
+ $refs = array();
+ foreach ($this->_values as $i => &$f) {
+ $refs[$i] = &$f;
+ }
+
+ $this->_stmt->store_result();
+ // bind to the result variables
+ call_user_func_array(
+ array($this->_stmt, 'bind_result'),
+ $this->_values
+ );
+ }
+ return $retval;
+ }
+
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws StatementExceptionMysqli
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ // fetch the next result
+ $retval = $this->_stmt->fetch();
+ switch ($retval) {
+ case null: // end of data
+ case false: // error occurred
+ $this->_stmt->reset();
+ return false;
+ default:
+ // fallthrough
+ }
+
+ // make sure we have a fetch mode
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ // dereference the result values, otherwise things like fetchAll()
+ // return the same values for every entry (because of the reference).
+ $values = array();
+ foreach ($this->_values as $key => $val) {
+ $values[] = $val;
+ }
+
+ $row = false;
+ switch ($style) {
+ case Db::FETCH_NUM:
+ $row = $values;
+ break;
+ case Db::FETCH_ASSOC:
+ $row = array_combine($this->_keys, $values);
+ break;
+ case Db::FETCH_BOTH:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ break;
+ case Db::FETCH_OBJ:
+ $row = (object) array_combine($this->_keys, $values);
+ break;
+ case Db::FETCH_BOUND:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ return $this->_fetchBound($row);
+ default:
+ throw new StatementExceptionMysqli("Invalid fetch mode '$style' specified");
+ }
+
+ return $row;
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws StatementExceptionMysqli
+ */
+ public function nextRowset()
+ {
+ /**
+ * @see StatementExceptionMysqli
+ */
+ throw new StatementExceptionMysqli(__FUNCTION__.'() is not implemented');
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ */
+ public function rowCount()
+ {
+ if (!$this->_adapter) {
+ return false;
+ }
+ $mysqli = $this->_adapter->getConnection();
+ return $mysqli->affected_rows;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/OracleStatement.php b/vendor/gipfl/zfdb/src/Statement/OracleStatement.php
new file mode 100644
index 0000000..532b227
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/OracleStatement.php
@@ -0,0 +1,519 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement;
+
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Statement;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+use gipfl\ZfDb\Statement\Exception\StatementExceptionOracle;
+
+/**
+ * Extends for Oracle.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class OracleStatement extends Statement
+{
+ /**
+ * Column names.
+ */
+ protected $_keys;
+
+ /**
+ * Fetched result values.
+ */
+ protected $_values;
+
+ /**
+ * Check if LOB field are returned as string
+ * instead of OCI-Lob object
+ *
+ * @var boolean
+ */
+ protected $_lobAsString = false;
+
+ /**
+ * Activate/deactivate return of LOB as string
+ *
+ * @param string $lob_as_string
+ * @return OracleStatement
+ */
+ public function setLobAsString($lob_as_string)
+ {
+ $this->_lobAsString = (bool) $lob_as_string;
+ return $this;
+ }
+
+ /**
+ * Return whether or not LOB are returned as string
+ *
+ * @return boolean
+ */
+ public function getLobAsString()
+ {
+ return $this->_lobAsString;
+ }
+
+ /**
+ * Prepares statement handle
+ *
+ * @param string $sql
+ * @return void
+ * @throws StatementExceptionOracle
+ */
+ protected function _prepare($sql)
+ {
+ $connection = $this->_adapter->getConnection();
+ $this->_stmt = @oci_parse($connection, $sql);
+ if (!$this->_stmt) {
+ throw new StatementExceptionOracle(oci_error($connection));
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementException
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ // default value
+ if ($type === null) {
+ $type = SQLT_CHR;
+ }
+
+ // default value
+ if ($length === null) {
+ $length = -1;
+ }
+
+ $retval = @oci_bind_by_name($this->_stmt, $parameter, $variable, $length, $type);
+ if ($retval === false) {
+ throw new StatementExceptionOracle(oci_error($this->_stmt));
+ }
+
+ return true;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ oci_free_statement($this->_stmt);
+ $this->_stmt = false;
+ return true;
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ return oci_num_fields($this->_stmt);
+ }
+
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = oci_error($this->_stmt);
+
+ if (!$error) {
+ return false;
+ }
+
+ return $error['code'];
+ }
+
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array|bool
+ */
+ public function errorInfo()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = oci_error($this->_stmt);
+ if (!$error) {
+ return false;
+ }
+
+ if (isset($error['sqltext'])) {
+ return array(
+ $error['code'],
+ $error['message'],
+ $error['offset'],
+ $error['sqltext'],
+ );
+ } else {
+ return array(
+ $error['code'],
+ $error['message'],
+ );
+ }
+ }
+
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws StatementException
+ */
+ public function _execute(array $params = null)
+ {
+ $connection = $this->_adapter->getConnection();
+
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($params !== null) {
+ if (!is_array($params)) {
+ $params = array($params);
+ }
+ $error = false;
+ foreach (array_keys($params) as $name) {
+ if (!$this->bindParam($name, $params[$name], null, -1)) {
+ $error = true;
+ break;
+ }
+ }
+ if ($error) {
+ throw new StatementExceptionOracle(oci_error($this->_stmt));
+ }
+ }
+
+ $retval = @oci_execute($this->_stmt, $this->_adapter->_getExecuteMode());
+ if ($retval === false) {
+ throw new StatementExceptionOracle(oci_error($this->_stmt));
+ }
+
+ $this->_keys = array();
+ if ($field_num = oci_num_fields($this->_stmt)) {
+ for ($i = 1; $i <= $field_num; $i++) {
+ $name = oci_field_name($this->_stmt, $i);
+ $this->_keys[] = $name;
+ }
+ }
+
+ $this->_values = array();
+ if ($this->_keys) {
+ $this->_values = array_fill(0, count($this->_keys), null);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed array, object, or scalar depending on fetch mode.
+ * @throws StatementException
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ $lob_as_string = $this->getLobAsString() ? OCI_RETURN_LOBS : 0;
+
+ switch ($style) {
+ case Db::FETCH_NUM:
+ $row = oci_fetch_array($this->_stmt, OCI_NUM | OCI_RETURN_NULLS | $lob_as_string);
+ break;
+ case Db::FETCH_ASSOC:
+ $row = oci_fetch_array($this->_stmt, OCI_ASSOC | OCI_RETURN_NULLS | $lob_as_string);
+ break;
+ case Db::FETCH_BOTH:
+ $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string);
+ break;
+ case Db::FETCH_OBJ:
+ $row = oci_fetch_object($this->_stmt);
+ break;
+ case Db::FETCH_BOUND:
+ $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string);
+ if ($row !== false) {
+ return $this->_fetchBound($row);
+ }
+ break;
+ default:
+ /**
+ * @see StatementExceptionOracle
+ */
+ throw new StatementExceptionOracle(
+ array(
+ 'code' => 'HYC00',
+ 'message' => "Invalid fetch mode '$style' specified"
+ )
+ );
+ break;
+ }
+
+ if (! $row && $error = oci_error($this->_stmt)) {
+ throw new StatementExceptionOracle($error);
+ }
+
+ if (is_array($row) && array_key_exists('zend_db_rownum', $row)) {
+ unset($row['zend_db_rownum']);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array|false Collection of rows, each in a format by the fetch mode.
+ * @throws StatementException
+ */
+ public function fetchAll($style = null, $col = 0)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ // make sure we have a fetch mode
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ $flags = OCI_FETCHSTATEMENT_BY_ROW;
+
+ switch ($style) {
+ case Db::FETCH_BOTH:
+ /**
+ * @see StatementExceptionOracle
+ */
+ throw new StatementExceptionOracle(
+ array(
+ 'code' => 'HYC00',
+ 'message' => "OCI8 driver does not support fetchAll(FETCH_BOTH), use fetch() in a loop instead"
+ )
+ );
+ // notreached
+ $flags |= OCI_NUM;
+ $flags |= OCI_ASSOC;
+ break;
+ case Db::FETCH_NUM:
+ $flags |= OCI_NUM;
+ break;
+ case Db::FETCH_ASSOC:
+ $flags |= OCI_ASSOC;
+ break;
+ case Db::FETCH_OBJ:
+ break;
+ case Db::FETCH_COLUMN:
+ $flags = $flags &~ OCI_FETCHSTATEMENT_BY_ROW;
+ $flags |= OCI_FETCHSTATEMENT_BY_COLUMN;
+ $flags |= OCI_NUM;
+ break;
+ default:
+ throw new StatementExceptionOracle(
+ array(
+ 'code' => 'HYC00',
+ 'message' => "Invalid fetch mode '$style' specified"
+ )
+ );
+ break;
+ }
+
+ $result = array();
+ if ($flags != OCI_FETCHSTATEMENT_BY_ROW) { /* not Zend_Db::FETCH_OBJ */
+ if (! ($rows = oci_fetch_all($this->_stmt, $result, 0, -1, $flags) )) {
+ if ($error = oci_error($this->_stmt)) {
+ throw new StatementExceptionOracle($error);
+ }
+ if (!$rows) {
+ return array();
+ }
+ }
+ if ($style == Db::FETCH_COLUMN) {
+ $result = $result[$col];
+ }
+ foreach ($result as &$row) {
+ if (is_array($row) && array_key_exists('zend_db_rownum', $row)) {
+ unset($row['zend_db_rownum']);
+ }
+ }
+ } else {
+ while (($row = oci_fetch_object($this->_stmt)) !== false) {
+ $result [] = $row;
+ }
+ if ($error = oci_error($this->_stmt)) {
+ throw new StatementExceptionOracle($error);
+ }
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws StatementException
+ */
+ public function fetchColumn($col = 0)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (!oci_fetch($this->_stmt)) {
+ // if no error, there is simply no record
+ if (!$error = oci_error($this->_stmt)) {
+ return false;
+ }
+ throw new StatementExceptionOracle($error);
+ }
+
+ $data = oci_result($this->_stmt, $col+1); //1-based
+ if ($data === false) {
+ throw new StatementExceptionOracle(oci_error($this->_stmt));
+ }
+
+ if ($this->getLobAsString()) {
+ // instanceof doesn't allow '-', we must use a temporary string
+ $type = 'OCI-Lob';
+ if ($data instanceof $type) {
+ $data = $data->read($data->size());
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws StatementException
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $obj = oci_fetch_object($this->_stmt);
+
+ if ($error = oci_error($this->_stmt)) {
+ throw new StatementExceptionOracle($error);
+ }
+
+ /* @todo XXX handle parameters */
+
+ return $obj;
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws StatementException
+ */
+ public function nextRowset()
+ {
+ /**
+ * @see StatementExceptionOracle
+ */
+ throw new StatementExceptionOracle(
+ array(
+ 'code' => 'HYC00',
+ 'message' => 'Optional feature not implemented'
+ )
+ );
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws StatementException
+ */
+ public function rowCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $num_rows = oci_num_rows($this->_stmt);
+
+ if ($num_rows === false) {
+ throw new StatementExceptionOracle(oci_error($this->_stmt));
+ }
+
+ return $num_rows;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Pdo/IbmPdoStatement.php b/vendor/gipfl/zfdb/src/Statement/Pdo/IbmPdoStatement.php
new file mode 100644
index 0000000..6e6d43a
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Pdo/IbmPdoStatement.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Pdo;
+
+use gipfl\ZfDb\Statement\PdoStatement;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+use PDOException;
+
+/**
+ * Proxy class to wrap a PDOStatement object for IBM Databases.
+ * Matches the interface of PDOStatement. All methods simply proxy to the
+ * matching method in PDOStatement. PDOExceptions thrown by PDOStatement
+ * are re-thrown as Zend_Db_Statement_Exception.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class IbmPdoStatement extends PdoStatement
+{
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws StatementException
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementException
+ */
+ public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ try {
+ if (($type === null) && ($length === null) && ($options === null)) {
+ return $this->_stmt->bindParam($parameter, $variable);
+ } else {
+ return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options);
+ }
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/Pdo/OciPdoStatement.php b/vendor/gipfl/zfdb/src/Statement/Pdo/OciPdoStatement.php
new file mode 100644
index 0000000..ed9b98c
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/Pdo/OciPdoStatement.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement\Pdo;
+
+use gipfl\ZfDb\Statement\PdoStatement;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+
+/**
+ * Proxy class to wrap a PDOStatement object for IBM Databases.
+ * Matches the interface of PDOStatement. All methods simply proxy to the
+ * matching method in PDOStatement. PDOExceptions thrown by PDOStatement
+ * are re-thrown as Zend_Db_Statement_Exception.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class OciPdoStatement extends PdoStatement
+{
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws StatementException
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('zend_db_rownum');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws StatementException
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ $row = parent::fetch($style, $cursor, $offset);
+
+ $remove = $this->_adapter->foldCase('zend_db_rownum');
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+
+ return $row;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/PdoStatement.php b/vendor/gipfl/zfdb/src/Statement/PdoStatement.php
new file mode 100644
index 0000000..ac49eb6
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/PdoStatement.php
@@ -0,0 +1,417 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement;
+
+use gipfl\ZfDb\Statement;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+use IteratorAggregate;
+use IteratorIterator;
+use PDO;
+use PDOException;
+
+/**
+ * Proxy class to wrap a PDOStatement object.
+ * Matches the interface of PDOStatement. All methods simply proxy to the
+ * matching method in PDOStatement. PDOExceptions thrown by PDOStatement
+ * are re-thrown as Zend_Db_Statement_Exception.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class PdoStatement extends Statement implements IteratorAggregate
+{
+ /**
+ * @var int
+ */
+ protected $_fetchMode = PDO::FETCH_ASSOC;
+
+ /**
+ * Prepare a string SQL statement and create a statement object.
+ *
+ * @param string $sql
+ * @return void
+ * @throws StatementException
+ */
+ protected function _prepare($sql)
+ {
+ try {
+ $this->_stmt = $this->_adapter->getConnection()->prepare($sql);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Bind a column of the statement result set to a PHP variable.
+ *
+ * @param string $column Name the column in the result set, either by
+ * position or by name.
+ * @param mixed $param Reference to the PHP variable containing the value.
+ * @param mixed $type OPTIONAL
+ * @return bool
+ * @throws StatementException
+ */
+ public function bindColumn($column, &$param, $type = null)
+ {
+ try {
+ if ($type === null) {
+ return $this->_stmt->bindColumn($column, $param);
+ } else {
+ return $this->_stmt->bindColumn($column, $param, $type);
+ }
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementException
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ try {
+ if ($type === null) {
+ if (is_bool($variable)) {
+ $type = PDO::PARAM_BOOL;
+ } elseif ($variable === null) {
+ $type = PDO::PARAM_NULL;
+ } elseif (is_integer($variable)) {
+ $type = PDO::PARAM_INT;
+ } else {
+ $type = PDO::PARAM_STR;
+ }
+ }
+ return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Binds a value to a parameter.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $value Scalar value to bind to the parameter.
+ * @param mixed $type OPTIONAL Datatype of the parameter.
+ * @return bool
+ * @throws StatementException
+ */
+ public function bindValue($parameter, $value, $type = null)
+ {
+ if (is_string($parameter) && $parameter[0] != ':') {
+ $parameter = ":$parameter";
+ }
+
+ $this->_bindParam[$parameter] = $value;
+
+ try {
+ if ($type === null) {
+ return $this->_stmt->bindValue($parameter, $value);
+ } else {
+ return $this->_stmt->bindValue($parameter, $value, $type);
+ }
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ * @throws StatementException
+ */
+ public function closeCursor()
+ {
+ try {
+ return $this->_stmt->closeCursor();
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ * @throws StatementException
+ */
+ public function columnCount()
+ {
+ try {
+ return $this->_stmt->columnCount();
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ * @throws StatementException
+ */
+ public function errorCode()
+ {
+ try {
+ return $this->_stmt->errorCode();
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ * @throws StatementException
+ */
+ public function errorInfo()
+ {
+ try {
+ return $this->_stmt->errorInfo();
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws StatementException
+ */
+ public function _execute(array $params = null)
+ {
+ try {
+ if ($params !== null) {
+ return $this->_stmt->execute($params);
+ } else {
+ return $this->_stmt->execute();
+ }
+ } catch (PDOException $e) {
+ $message = sprintf('%s, query was: %s', $e->getMessage(), $this->_stmt->queryString);
+ throw new StatementException($message, (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws StatementException
+ */
+ public function fetch($style = null, $cursor = PDO::FETCH_ORI_NEXT, $offset = 0)
+ {
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+ try {
+ return $this->_stmt->fetch($style, $cursor, $offset);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Required by IteratorAggregate interface
+ *
+ * @return IteratorIterator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return new IteratorIterator($this->_stmt);
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws StatementException
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+ try {
+ if ($style == PDO::FETCH_COLUMN) {
+ if ($col === null) {
+ $col = 0;
+ }
+ return $this->_stmt->fetchAll($style, $col);
+ } else {
+ return $this->_stmt->fetchAll($style);
+ }
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws StatementException
+ */
+ public function fetchColumn($col = 0)
+ {
+ try {
+ return $this->_stmt->fetchColumn($col);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws StatementException
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ try {
+ return $this->_stmt->fetchObject($class, $config);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieve a statement attribute.
+ *
+ * @param integer $key Attribute name.
+ * @return mixed Attribute value.
+ * @throws StatementException
+ */
+ public function getAttribute($key)
+ {
+ try {
+ return $this->_stmt->getAttribute($key);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns metadata for a column in a result set.
+ *
+ * @param int $column
+ * @return mixed
+ * @throws StatementException
+ */
+ public function getColumnMeta($column)
+ {
+ try {
+ return $this->_stmt->getColumnMeta($column);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws StatementException
+ */
+ public function nextRowset()
+ {
+ try {
+ return $this->_stmt->nextRowset();
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws StatementException
+ */
+ public function rowCount()
+ {
+ try {
+ return $this->_stmt->rowCount();
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Set a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @param mixed $val Attribute value.
+ * @return bool
+ * @throws StatementException
+ */
+ public function setAttribute($key, $val)
+ {
+ try {
+ return $this->_stmt->setAttribute($key, $val);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Set the default fetch mode for this statement.
+ *
+ * @param int $mode The fetch mode.
+ * @return bool
+ * @throws StatementException
+ */
+ public function setFetchMode($mode)
+ {
+ $this->_fetchMode = $mode;
+ try {
+ return $this->_stmt->setFetchMode($mode);
+ } catch (PDOException $e) {
+ throw new StatementException($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/SqlsrvStatement.php b/vendor/gipfl/zfdb/src/Statement/SqlsrvStatement.php
new file mode 100644
index 0000000..d5fe23a
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/SqlsrvStatement.php
@@ -0,0 +1,423 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement;
+
+use gipfl\ZfDb\Db;
+use gipfl\ZfDb\Statement\Exception\StatementException;
+use gipfl\ZfDb\Statement\Exception\StatementExceptionSqlsrv;
+use gipfl\ZfDb\Statement;
+
+/**
+ * Extends for Microsoft SQL Server Driver for PHP
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class SqlsrvStatement extends Statement
+{
+ /**
+ * The connection_stmt object original string.
+ */
+ protected $_originalSQL;
+
+ /**
+ * Column names.
+ */
+ protected $_keys;
+
+ /**
+ * Query executed
+ */
+ protected $_executed = false;
+
+ /**
+ * Prepares statement handle
+ *
+ * @param string $sql
+ * @return void
+ * @throws StatementExceptionSqlsrv
+ */
+ protected function _prepare($sql)
+ {
+ $connection = $this->_adapter->getConnection();
+
+ $this->_stmt = sqlsrv_prepare($connection, $sql);
+
+ if (!$this->_stmt) {
+ throw new StatementExceptionSqlsrv(sqlsrv_errors());
+ }
+
+ $this->_originalSQL = $sql;
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementException
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ //Sql server doesn't support bind by name
+ return true;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ sqlsrv_free_stmt($this->_stmt);
+ $this->_stmt = false;
+ return true;
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if ($this->_stmt && $this->_executed) {
+ return sqlsrv_num_fields($this->_stmt);
+ }
+
+ return 0;
+ }
+
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = sqlsrv_errors();
+ if (!$error) {
+ return false;
+ }
+
+ return $error[0]['code'];
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array|bool
+ */
+ public function errorInfo()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = sqlsrv_errors();
+ if (!$error) {
+ return false;
+ }
+
+ return array(
+ $error[0]['code'],
+ $error[0]['message'],
+ );
+ }
+
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws StatementException
+ */
+ public function _execute(array $params = null)
+ {
+ $connection = $this->_adapter->getConnection();
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($params !== null) {
+ if (!is_array($params)) {
+ $params = array($params);
+ }
+ $error = false;
+
+ // make all params passed by reference
+ $params_ = array();
+ $temp = array();
+ $i = 1;
+ foreach ($params as $param) {
+ $temp[$i] = $param;
+ $params_[] = &$temp[$i];
+ $i++;
+ }
+ $params = $params_;
+ }
+
+ $this->_stmt = sqlsrv_query($connection, $this->_originalSQL, $params);
+
+ if (!$this->_stmt) {
+ throw new StatementExceptionSqlsrv(sqlsrv_errors());
+ }
+
+ $this->_executed = true;
+
+ return (!$this->_stmt);
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws StatementException
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (null === $style) {
+ $style = $this->_fetchMode;
+ }
+
+ $values = sqlsrv_fetch_array($this->_stmt, SQLSRV_FETCH_ASSOC);
+
+ if (!$values && (null !== $error = sqlsrv_errors())) {
+ throw new StatementExceptionSqlsrv($error);
+ }
+
+ if (null === $values) {
+ return null;
+ }
+
+ if (!$this->_keys) {
+ foreach ($values as $key => $value) {
+ $this->_keys[] = $this->_adapter->foldCase($key);
+ }
+ }
+
+ $values = array_values($values);
+
+ $row = false;
+ switch ($style) {
+ case Db::FETCH_NUM:
+ $row = $values;
+ break;
+ case Db::FETCH_ASSOC:
+ $row = array_combine($this->_keys, $values);
+ break;
+ case Db::FETCH_BOTH:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ break;
+ case Db::FETCH_OBJ:
+ $row = (object) array_combine($this->_keys, $values);
+ break;
+ case Db::FETCH_BOUND:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ $row = $this->_fetchBound($row);
+ break;
+ default:
+ throw new StatementExceptionSqlsrv("Invalid fetch mode '$style' specified");
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws StatementException
+ */
+ public function fetchColumn($col = 0)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (!sqlsrv_fetch($this->_stmt)) {
+ if (null !== $error = sqlsrv_errors()) {
+ throw new StatementExceptionSqlsrv($error);
+ }
+
+ // If no error, there is simply no record
+ return false;
+ }
+
+ $data = sqlsrv_get_field($this->_stmt, $col); //0-based
+ if ($data === false) {
+ throw new StatementExceptionSqlsrv(sqlsrv_errors());
+ }
+
+ return $data;
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws StatementException
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $obj = sqlsrv_fetch_object($this->_stmt);
+
+ if ($error = sqlsrv_errors()) {
+ throw new StatementExceptionSqlsrv($error);
+ }
+
+ /* @todo XXX handle parameters */
+
+ if (null === $obj) {
+ return false;
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Returns metadata for a column in a result set.
+ *
+ * @param int $column
+ * @return mixed
+ * @throws StatementExceptionSqlsrv
+ */
+ public function getColumnMeta($column)
+ {
+ $fields = sqlsrv_field_metadata($this->_stmt);
+
+ if (!$fields) {
+ throw new StatementExceptionSqlsrv('Column metadata can not be fetched');
+ }
+
+ if (!isset($fields[$column])) {
+ throw new StatementExceptionSqlsrv('Column index does not exist in statement');
+ }
+
+ return $fields[$column];
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws StatementException
+ */
+ public function nextRowset()
+ {
+ if (sqlsrv_next_result($this->_stmt) === false) {
+ throw new StatementExceptionSqlsrv(sqlsrv_errors());
+ }
+
+ // reset column keys
+ $this->_keys = null;
+
+ return true;
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws StatementException
+ */
+ public function rowCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (!$this->_executed) {
+ return 0;
+ }
+
+ $num_rows = sqlsrv_rows_affected($this->_stmt);
+
+ // Strict check is necessary; 0 is a valid return value
+ if ($num_rows === false) {
+ throw new StatementExceptionSqlsrv(sqlsrv_errors());
+ }
+
+ return $num_rows;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+}
diff --git a/vendor/gipfl/zfdb/src/Statement/StatementInterface.php b/vendor/gipfl/zfdb/src/Statement/StatementInterface.php
new file mode 100644
index 0000000..3b2a0b4
--- /dev/null
+++ b/vendor/gipfl/zfdb/src/Statement/StatementInterface.php
@@ -0,0 +1,198 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+namespace gipfl\ZfDb\Statement;
+
+use gipfl\ZfDb\Statement\Exception\StatementException;
+
+/**
+ * Emulates a PDOStatement for native database adapters.
+ *
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface StatementInterface
+{
+ /**
+ * Bind a column of the statement result set to a PHP variable.
+ *
+ * @param string $column Name the column in the result set, either by
+ * position or by name.
+ * @param mixed $param Reference to the PHP variable containing the value.
+ * @param mixed $type OPTIONAL
+ * @return bool
+ * @throws StatementException
+ */
+ public function bindColumn($column, &$param, $type = null);
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws StatementException
+ */
+ public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null);
+
+ /**
+ * Binds a value to a parameter.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $value Scalar value to bind to the parameter.
+ * @param mixed $type OPTIONAL Datatype of the parameter.
+ * @return bool
+ * @throws StatementException
+ */
+ public function bindValue($parameter, $value, $type = null);
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ * @throws StatementException
+ */
+ public function closeCursor();
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ * @throws StatementException
+ */
+ public function columnCount();
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ * @throws StatementException
+ */
+ public function errorCode();
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ * @throws StatementException
+ */
+ public function errorInfo();
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws StatementException
+ */
+ public function execute(array $params = array());
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws StatementException
+ */
+ public function fetch($style = null, $cursor = null, $offset = null);
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws StatementException
+ */
+ public function fetchAll($style = null, $col = null);
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws StatementException
+ */
+ public function fetchColumn($col = 0);
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws StatementException
+ */
+ public function fetchObject($class = 'stdClass', array $config = array());
+
+ /**
+ * Retrieve a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @return mixed Attribute value.
+ * @throws StatementException
+ */
+ public function getAttribute($key);
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws StatementException
+ */
+ public function nextRowset();
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws StatementException
+ */
+ public function rowCount();
+
+ /**
+ * Set a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @param mixed $val Attribute value.
+ * @return bool
+ * @throws StatementException
+ */
+ public function setAttribute($key, $val);
+
+ /**
+ * Set the default fetch mode for this statement.
+ *
+ * @param int $mode The fetch mode.
+ * @return bool
+ * @throws StatementException
+ */
+ public function setFetchMode($mode);
+}
diff --git a/vendor/gipfl/zfdbstore/composer.json b/vendor/gipfl/zfdbstore/composer.json
new file mode 100644
index 0000000..dc84939
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/composer.json
@@ -0,0 +1,16 @@
+{
+ "name": "gipfl/zfdbstore",
+ "description": "Storable class helpers for ZfDb",
+ "type": "library",
+ "require": {
+ "php": ">=5.6"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "gipfl\\ZfDbStore\\": "src"
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/BaseStore.php b/vendor/gipfl/zfdbstore/src/BaseStore.php
new file mode 100644
index 0000000..176ec33
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/BaseStore.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use Evenement\EventEmitterTrait;
+use RuntimeException;
+
+/**
+ * Class BaseStore
+ *
+ * This class handles creation/update/delete of $storable
+ * and save records them into a log table in the DB
+ */
+abstract class BaseStore implements Store
+{
+ use EventEmitterTrait;
+
+ /**
+ * If a new element is created, it stores it into the DB
+ * and emits the "insert" event, in order to create the corresponding activity log
+ *
+ * If an element is updated, it updates the DB record and
+ * emits the "update" event
+ *
+ * @param StorableInterface $storable
+ * @return bool Whether the store() applied any change to the stored object
+ */
+ public function store(StorableInterface $storable)
+ {
+ $affected = false;
+
+ if ($storable->isNew()) {
+ $this->insertIntoStore($storable);
+ $this->emit('insert', [$storable]);
+ $affected = true;
+ } else {
+ if ($storable->isModified() && $this->updateStore($storable)) {
+ $this->emit('update', [$storable]);
+ $affected = true;
+ }
+ }
+ $storable->setStored();
+
+ return $affected;
+ }
+
+ /**
+ * If a new element is deleted, it deletes the record from the DB
+ * and emits the "delete" event, in order to create the corresponding activity log
+ *
+ * @param StorableInterface $storable
+ * @return bool
+ */
+ public function delete(StorableInterface $storable)
+ {
+ if ($this->deleteFromStore($storable)) {
+ $this->emit('delete', [$storable]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Loads $storable by it's key property/properties
+ *
+ * @param StorableInterface $storable
+ * @param $key
+ * @return StorableInterface
+ */
+ abstract protected function loadFromStore(StorableInterface $storable, $key);
+
+ /**
+ * Deletes this record from the store
+ *
+ * @param StorableInterface $storable
+ */
+ abstract protected function deleteFromStore(StorableInterface $storable);
+
+ /**
+ * Inserts the $storable, refreshes the object in case storing implies
+ * changes (like auto-incremental IDs)
+ *
+ * @param StorableInterface $storable
+ * @return bool
+ * @throws \gipfl\ZfDb\Adapter\Exception\AdapterException
+ * @throws \gipfl\ZfDb\Statement\Exception\StatementException
+ * @throws \Zend_Db_Adapter_Exception
+ */
+ abstract protected function insertIntoStore(StorableInterface $storable);
+ abstract protected function updateStore(StorableInterface $storable);
+
+ /**
+ * Load $storable from DB
+ *
+ * @param array|string $key
+ * @param string $className
+ * @return StorableInterface
+ */
+ public function load($key, $className)
+ {
+ $storable = new $className();
+ if ($storable instanceof StorableInterface) {
+ $storable = $storable::create();
+ return $this->loadFromStore($storable, $key);
+ } else {
+ throw new RuntimeException(
+ get_class($this) . "::load: '$className' is not a StorableInterface implementation"
+ );
+ }
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/DbStorable.php b/vendor/gipfl/zfdbstore/src/DbStorable.php
new file mode 100644
index 0000000..4bd1783
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/DbStorable.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use RuntimeException;
+
+/**
+ * DbStorable
+ *
+ * This trait provides all you need to create an object implementing the
+ * DbStorableInterface
+ */
+trait DbStorable
+{
+ use Storable;
+
+ // protected $tableName;
+
+ public function getTableName()
+ {
+ if (isset($this->tableName)) {
+ return $this->tableName;
+ } else {
+ throw new RuntimeException('A DbStorable needs a tableName');
+ }
+ }
+
+ public function hasAutoIncKey()
+ {
+ return $this->getAutoIncKeyName() !== null;
+ }
+
+ public function getAutoIncKeyName()
+ {
+ if (isset($this->autoIncKeyName)) {
+ return $this->autoIncKeyName;
+ } else {
+ return null;
+ }
+ }
+
+ protected function requireAutoIncKeyName()
+ {
+ $key = $this->getAutoIncKeyName();
+ if ($key === null) {
+ throw new RuntimeException('This DbStorable has no autoinc key');
+ }
+
+ return $key;
+ }
+
+ public function getAutoIncId()
+ {
+ $key = $this->requireAutoIncKeyName();
+ if (isset($this->properties[$key])) {
+ return (int) $this->properties[$key];
+ }
+
+ return null;
+ }
+
+ protected function forgetAutoIncId()
+ {
+ $key = $this->requireAutoIncKeyName();
+ if (isset($this->properties[$key])) {
+ $this->properties[$key] = null;
+ }
+
+ return $this;
+ }
+
+ public function __invoke($properties = [])
+ {
+ $storable = new static();
+ $storable->setProperties($properties);
+
+ return $storable;
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/DbStorableInterface.php b/vendor/gipfl/zfdbstore/src/DbStorableInterface.php
new file mode 100644
index 0000000..ba9a49f
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/DbStorableInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+interface DbStorableInterface extends StorableInterface
+{
+ /**
+ * The table where this Storable will be loaded from and stored to
+ *
+ * @return string
+ */
+ public function getTableName();
+
+ /**
+ * Whether this Storable has an auto-incrementing key column
+ * @return bool
+ */
+ public function hasAutoIncKey();
+
+ /**
+ * Returns the name of the auto-incrementing key column
+ *
+ * @return string
+ */
+ public function getAutoIncKeyName();
+
+ /**
+ * Get the AutoInc value if set
+ *
+ * Should throw and Exception in case no such key has been defined. This
+ * will return null for unstored DbStorables
+ *
+ * @return int|null
+ */
+ public function getAutoIncId();
+}
diff --git a/vendor/gipfl/zfdbstore/src/NotFoundError.php b/vendor/gipfl/zfdbstore/src/NotFoundError.php
new file mode 100644
index 0000000..fb2413e
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/NotFoundError.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use Exception;
+
+class NotFoundError extends Exception
+{
+}
diff --git a/vendor/gipfl/zfdbstore/src/Storable.php b/vendor/gipfl/zfdbstore/src/Storable.php
new file mode 100644
index 0000000..9980efd
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/Storable.php
@@ -0,0 +1,323 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * Trait Storable
+ *
+ * This trait implements a generic trait used for storing
+ * information about users activity (i.e. creation of new elements,
+ * update/delete existing records)
+ *
+ * Each storable is characterized by:
+ * - $defaultProperties (array of properties set by default)
+ * - $modifiedProperties (array of properties modified by the user)
+ * - $storedProperties (array of properties loaded from the DB)
+ * - key property represents the primary key in the DB
+ */
+trait Storable
+{
+ /** @var null|array */
+ protected $storedProperties;
+
+ ///** @var array */
+ //protected $defaultProperties = [];
+
+ /** @var array */
+ protected $modifiedProperties = [];
+
+ /** @var array */
+ protected $properties = [];
+
+ ///** @var string|array */
+ //protected $keyProperty;
+
+ /**
+ * If a $storable has no stored properties it means that
+ * it is a new element -> the user is creating it right now
+ *
+ * @return bool
+ */
+ public function isNew()
+ {
+ return null === $this->storedProperties;
+ }
+
+ /**
+ * This function returns the key property (it can be an array of properties) of the $storable
+ * i.e. it returns the primary key in the case of DB object
+ *
+ * @return array|mixed
+ */
+ public function getKey()
+ {
+ $property = $this->getKeyProperty();
+ if (is_string($property)) {
+ return $this->get($property);
+ } else {
+ return $this->getProperties($property);
+ }
+ }
+
+ /**
+ * @return string|array
+ */
+ public function getKeyProperty()
+ {
+ if (isset($this->keyProperty)) {
+ return $this->keyProperty;
+ } else {
+ throw new RuntimeException('A storable needs a key property.');
+ }
+ }
+
+ /**
+ * Create a $storable setting its properties
+ *
+ * @param array $properties
+ * @return static
+ */
+ public static function create(array $properties = [])
+ {
+ $storable = new static();
+ $storable->properties = $storable->getDefaultProperties();
+ $storable->setProperties($properties);
+
+ return $storable;
+ }
+
+ /**
+ * Loads an already existing $storable
+ *
+ * @param Store $store
+ * @param $key
+ * @return mixed
+ */
+ public static function load(Store $store, $key)
+ {
+ return $store->load($key, get_called_class());
+ return $store->load(get_called_class(), $key);
+ }
+
+ /**
+ * Returns the value of $property (if this property exists)
+ *
+ * @param $property
+ * @return mixed
+ */
+ public function get($property, $default = null)
+ {
+ $this->assertPropertyExists($property);
+
+ if (array_key_exists($property, $this->properties)) {
+ if ($this->properties[$property] === null) {
+ return $default;
+ } else {
+ return $this->properties[$property];
+ }
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Returns the array of values corresponding to the requested array of properties
+ *
+ * @param array|null $properties
+ * @return array
+ */
+ public function getProperties(array $properties = null)
+ {
+ if ($properties === null) {
+ $properties = array_keys($this->properties);
+ }
+
+ $result = [];
+ foreach ($properties as $property) {
+ $result[$property] = $this->get($property);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the array of properties modified by the user
+ *
+ * @return array
+ */
+ public function getModifiedProperties()
+ {
+ return $this->getProperties($this->listModifiedProperties());
+ }
+
+ /**
+ * Returns the array of stored properties
+ * It can be used only in case of already existing $storable
+ */
+ public function getStoredProperties()
+ {
+ if ($this->isNew()) {
+ throw new RuntimeException(
+ 'Trying to access stored properties of an unstored Storable'
+ );
+ }
+
+ return $this->storedProperties;
+ }
+
+ /**
+ * Set the value of a given property
+ *
+ * @param $property
+ * @param $value
+ * @return bool
+ */
+ public function set($property, $value)
+ {
+ $this->assertPropertyExists($property);
+
+ if ($value === $this->get($property)) {
+ return false;
+ }
+
+ $this->properties[$property] = $value;
+
+ if ($this->storedProperties !== null && $this->storedProperties[$property] === $value) {
+ $this->resetModifiedProperty($property);
+ } else {
+ $this->setModifiedProperty($property);
+ }
+
+ return true;
+ }
+
+ /**
+ * Initialize the stored property at the first loading of the $storable element
+ *
+ * @param $property
+ * @param $value
+ */
+ public function setStoredProperty($property, $value)
+ {
+ $this->assertPropertyExists($property);
+
+ $this->storedProperties[$property] = $value;
+ $this->properties[$property] = $value;
+ unset($this->modifiedProperties[$property]);
+ }
+
+ /**
+ * Set array of values for the given array of properties
+ *
+ * @param array $properties
+ * @return $this
+ */
+ public function setProperties(array $properties)
+ {
+ foreach ($properties as $property => $value) {
+ $this->set($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Initialize the stored property array
+ *
+ * @param array $properties
+ * @return $this
+ */
+ public function setStoredProperties(array $properties)
+ {
+ foreach ($properties as $property => $value) {
+ $this->setStoredProperty($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param $property
+ */
+ public function assertPropertyExists($property)
+ {
+ if (! $this->hasProperty($property)) {
+ throw new InvalidArgumentException(sprintf(
+ "Trying to access invalid property '%s'",
+ $property
+ ));
+ }
+ }
+
+ /**
+ * @param $property
+ * @return bool
+ */
+ public function hasProperty($property)
+ {
+ return array_key_exists($property, $this->defaultProperties);
+ }
+
+ /**
+ * @param $property
+ */
+ private function setModifiedProperty($property)
+ {
+ $this->modifiedProperties[$property] = true;
+ }
+
+ /**
+ * @param $property
+ */
+ private function resetModifiedProperty($property)
+ {
+ unset($this->modifiedProperties[$property]);
+ }
+
+ /**
+ * Check if $storable has changed,
+ * if not the $modifiedProperties array is empty
+ *
+ * @return bool
+ */
+ public function isModified()
+ {
+ return !empty($this->modifiedProperties);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getDefaultProperties()
+ {
+ if (isset($this->defaultProperties)) {
+ return $this->defaultProperties;
+ } else {
+ throw new RuntimeException('A storable needs default properties.');
+ }
+ }
+
+ /**
+ * Get the array key of the modifies properties
+ *
+ * @return array
+ */
+ public function listModifiedProperties()
+ {
+ return array_keys($this->modifiedProperties);
+ }
+
+ /**
+ * @return $this
+ */
+ public function setStored()
+ {
+ $this->storedProperties = $this->properties;
+ $this->modifiedProperties = [];
+
+ return $this;
+ }
+}
diff --git a/vendor/gipfl/zfdbstore/src/StorableInterface.php b/vendor/gipfl/zfdbstore/src/StorableInterface.php
new file mode 100644
index 0000000..8ae49d2
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/StorableInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+interface StorableInterface
+{
+ public function isNew();
+
+ public function getKey();
+
+ public function getKeyProperty();
+
+ public static function create(array $properties = []);
+
+ public static function load(Store $store, $key);
+
+ public function get($property);
+
+ public function getProperties(array $properties = null);
+
+ public function hasProperty($property);
+
+ public function getModifiedProperties();
+
+ public function getStoredProperties();
+
+ public function set($property, $value);
+
+ public function setStoredProperty($property, $value);
+
+ public function setProperties(array $properties);
+
+ public function setStoredProperties(array $properties);
+
+ public function assertPropertyExists($property);
+
+ public function isModified();
+
+ public function getDefaultProperties();
+
+ public function listModifiedProperties();
+
+ public function setStored();
+}
diff --git a/vendor/gipfl/zfdbstore/src/Store.php b/vendor/gipfl/zfdbstore/src/Store.php
new file mode 100644
index 0000000..cdc7ab1
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/Store.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+interface Store
+{
+ /**
+ * Function used for saving changes and log the activity.
+ * To be extended as needed (see BaseStore.php)
+ *
+ * @param StorableInterface $storable
+ * @return mixed
+ */
+ public function store(StorableInterface $storable);
+
+ /**
+ * @param array|string $key
+ * @param string $className
+ * @return StorableInterface
+ */
+ public function load($key, $className);
+ public function exists(StorableInterface $storable);
+ public function delete(StorableInterface $storable);
+}
diff --git a/vendor/gipfl/zfdbstore/src/ZfDbStore.php b/vendor/gipfl/zfdbstore/src/ZfDbStore.php
new file mode 100644
index 0000000..d34541c
--- /dev/null
+++ b/vendor/gipfl/zfdbstore/src/ZfDbStore.php
@@ -0,0 +1,241 @@
+<?php
+
+namespace gipfl\ZfDbStore;
+
+use InvalidArgumentException;
+use RuntimeException;
+use gipfl\ZfDb\Adapter\Adapter;
+use Zend_Db_Adapter_Abstract as ZfDb;
+use function array_key_exists;
+use function assert;
+use function implode;
+use function is_array;
+use function is_string;
+use function method_exists;
+
+/**
+ * Class DbStore
+ *
+ * Extends BaseStore for DB object
+ */
+class ZfDbStore extends BaseStore
+{
+ /** @var Adapter|ZfDb */
+ protected $db;
+
+ /**
+ * ZfDbStore constructor.
+ * @param Adapter|ZfDb $db
+ */
+ public function __construct($db)
+ {
+ if ($db instanceof Adapter || $db instanceof ZfDb) {
+ $this->db = $db;
+ } else {
+ throw new InvalidArgumentException('ZfDb Adapter is required');
+ }
+ }
+
+ /**
+ * @return Adapter|ZfDb
+ */
+ public function getDb()
+ {
+ return $this->db;
+ }
+
+ /**
+ * Checks whether the passed $storable already exists in the DB
+ *
+ * @param DbStorableInterface $storable
+ * @return bool
+ */
+ public function exists(StorableInterface $storable)
+ {
+ return (int) $this->db->fetchOne(
+ $this->db
+ ->select()
+ ->from($this->getTableName($storable), '(1)')
+ ->where($this->createWhere($storable))
+ ) === 1;
+ }
+
+ /**
+ * @param DbStorableInterface $storable
+ * @param string|null $keyColumn
+ * @param string|null $labelColumn
+ * @return array
+ */
+ public function enum(StorableInterface $storable, $keyColumn = null, $labelColumn = null)
+ {
+ assert($storable instanceof DbStorableInterface);
+ if ($keyColumn === null) {
+ $key = $storable->getKeyProperty();
+ if (is_array($key)) {
+ if ($storable->hasAutoIncKey()) {
+ $key = $storable->getAutoIncKeyName();
+ } else {
+ throw new InvalidArgumentException(
+ 'Cannot provide an enum for a multi-key column'
+ );
+ }
+ }
+ } else {
+ $key = $keyColumn;
+ }
+
+ if ($labelColumn === null) {
+ if (method_exists($storable, 'getDisplayColumn')) {
+ $label = $storable->getDisplayColumn();
+ } else {
+ $label = $storable->getKeyProperty();
+ if (is_array($label)) {
+ $label = $key;
+ }
+ }
+ } else {
+ $label = $labelColumn;
+ }
+
+ $columns = [
+ 'key_col' => $key,
+ 'label_col' => $label
+ ];
+
+ $query = $this->db->select()->from(
+ $this->getTableName($storable),
+ $columns
+ );
+
+ return $this->db->fetchPairs($query);
+ }
+
+ protected function insertIntoStore(StorableInterface $storable)
+ {
+ assert($storable instanceof DbStorableInterface);
+ $result = $this->db->insert(
+ $this->getTableName($storable),
+ $storable->getProperties()
+ );
+ /** @var DbStorable $storable */
+ if ($storable->hasAutoIncKey()) {
+ $storable->set(
+ $storable->getAutoIncKeyName(),
+ $this->db->lastInsertId($this->getTableName($storable))
+ );
+ }
+
+ return $result > 0;
+ }
+
+ protected function updateStore(StorableInterface $storable)
+ {
+ assert($storable instanceof DbStorableInterface);
+ $this->db->update(
+ $this->getTableName($storable),
+ $storable->getProperties(),
+ $this->createWhere($storable)
+ );
+
+ return true;
+ }
+
+ protected function deleteFromStore(StorableInterface $storable)
+ {
+ assert($storable instanceof DbStorableInterface);
+ return $this->db->delete(
+ $this->getTableName($storable),
+ $this->createWhere($storable)
+ );
+ }
+
+ protected function loadFromStore(StorableInterface $storable, $key)
+ {
+ assert($storable instanceof DbStorableInterface);
+ $keyColumn = $storable->getKeyProperty();
+ $select = $this->db->select()->from($this->getTableName($storable));
+
+ if (is_string($keyColumn)) {
+ $select->where("$keyColumn = ?", $key);
+ } else {
+ foreach ($keyColumn as $column) {
+ if (array_key_exists($column, $key)) {
+ $select->where("$column = ?", $key[$column]);
+ } else {
+ throw new RuntimeException('Multicolumn key required, got no %s', $column);
+ }
+ }
+ }
+
+ $result = $this->db->fetchAll($select);
+ // TODO: properties should be changed in storeProperties
+ // when you load the element from db before changing it.
+ if (empty($result)) {
+ throw new NotFoundError('Not found: ' . $this->describeKey($storable, $key));
+ }
+
+ if (count($result) > 1) {
+ throw new NotFoundError(sprintf(
+ 'One row expected, got %s: %s',
+ count($result),
+ $this->describeKey($storable, $key)
+ ));
+ }
+
+ $storable->setStoredProperties((array) $result[0]);
+
+ return $storable;
+ }
+
+ protected function describeKey(StorableInterface $storable, $key)
+ {
+
+ assert($storable instanceof DbStorableInterface);
+ $keyColumn = $storable->getKeyProperty();
+ if (is_string($keyColumn)) {
+ return (string) $key;
+ }
+
+ $parts = [];
+ foreach ($keyColumn as $column) {
+ if (array_key_exists($column, $key)) {
+ $parts[$column] = $key[$column];
+ } else {
+ $parts[$column] = '?';
+ }
+ }
+ return implode(', ', $parts);
+ }
+
+ /**
+ * Returns $storable table name
+ *
+ * @param DbStorableInterface $storable
+ * @return string
+ */
+ protected function getTableName(DbStorableInterface $storable)
+ {
+ return $storable->getTableName();
+ }
+
+ /**
+ * @param DbStorableInterface $storable
+ * @return string
+ */
+ protected function createWhere($storable)
+ {
+ $where = [];
+ foreach ((array) $storable->getKeyProperty() as $key) {
+ $value = $storable->get($key);
+ // TODO, eventually:
+ // $key = $this->db->quoteIdentifier($key);
+ if ($value === null) {
+ $where[] = "$key IS NULL";
+ } else {
+ $where[] = $this->db->quoteInto("$key = ?", $value);
+ }
+ }
+
+ return implode(' AND ', $where);
+ }
+}