From 6f84bb5f61ca66373bc062dd92f515b4664f7327 Mon Sep 17 00:00:00 2001 From: Tanner Jones Date: Sun, 22 Feb 2026 14:13:20 -0700 Subject: [PATCH 01/13] gitignore update --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 73d3b9f6..5f2f1f58 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ Thumbs.db # Wine prefix folder if using Wine to run the server on Linux with bin/wine-server /wine/ + +/local/ +/logs/ \ No newline at end of file From 818ed8b11ee615aab2b749b7a68c6270e306bef0 Mon Sep 17 00:00:00 2001 From: Emmanuel Asuquo Date: Tue, 3 Mar 2026 13:46:38 -0500 Subject: [PATCH 02/13] Initial upgrade and short open tags fixed --- docker/config/php.ini | 748 ++++++++++++++------------------- docker/dev/Dockerfile | 3 +- htdocs/healthcheck.php | 2 +- htdocs/includes/page_elems.php | 4 +- htdocs/regn/new_patient.php | 2 +- 5 files changed, 327 insertions(+), 432 deletions(-) diff --git a/docker/config/php.ini b/docker/config/php.ini index 9a613824..f5b36ca1 100644 --- a/docker/config/php.ini +++ b/docker/config/php.ini @@ -1,5 +1,5 @@ ; BLIS Notes -; The original file comes from the default PHP 5.6 config from ondrej/php PPA on Ubuntu Focal. +; The original file comes from the default PHP 7.4 config from ondrej/php PPA on Ubuntu Focal. ; Modifications are made below. [PHP] @@ -19,7 +19,7 @@ ; 5. The web server's directory (for SAPI modules), or directory of PHP ; (otherwise in Windows) ; 6. The directory from the --with-config-file-path compile time option, or the -; Windows directory (C:\windows or C:\winnt) +; Windows directory (usually C:\windows) ; See the PHP docs for more specific information. ; http://php.net/configuration.file @@ -62,9 +62,9 @@ ; An empty string can be denoted by simply not writing anything after the equal ; sign, or by using the None keyword: -; foo = ; sets foo to an empty string -; foo = None ; sets foo to an empty string -; foo = "None" ; sets foo to the string 'None' +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' ; If you use constants in your value, and these constants belong to a ; dynamically loaded extension (either a PHP extension or a Zend extension), @@ -87,7 +87,7 @@ ; development version only in development environments, as errors shown to ; application users can inadvertently leak otherwise secure information. -; This is php.ini-production INI file. +; This is the php.ini-production INI file. ;;;;;;;;;;;;;;;;;;; ; Quick Reference ; @@ -112,11 +112,6 @@ ; Development Value: E_ALL ; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT -; html_errors -; Default Value: On -; Development Value: On -; Production value: On - ; log_errors ; Default Value: Off ; Development Value: On @@ -147,7 +142,7 @@ ; Development Value: 1000 ; Production Value: 1000 -; session.hash_bits_per_character +; session.sid_bits_per_character ; Default Value: 4 ; Development Value: 5 ; Production Value: 5 @@ -157,16 +152,6 @@ ; Development Value: Off ; Production Value: Off -; track_errors -; Default Value: Off -; Development Value: On -; Production Value: Off - -; url_rewriter.tags -; Default Value: "a=href,area=href,frame=src,form=,fieldset=" -; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" -; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" - ; variables_order ; Default Value: "EGPCS" ; Development Value: "GPCS" @@ -178,7 +163,7 @@ ; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" ;user_ini.filename = ".user.ini" -; To disable this feature set this option to empty value +; To disable this feature set this option to an empty value ;user_ini.filename = ; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) @@ -203,11 +188,7 @@ engine = On ; Development Value: Off ; Production Value: Off ; http://php.net/short-open-tag -short_open_tag = On - -; Allow ASP-style <% %> tags. -; http://php.net/asp-tags -asp_tags = Off +short_open_tag = Off ; The number of significant digits displayed in floating point numbers. ; http://php.net/precision @@ -252,6 +233,23 @@ output_buffering = 4096 ; http://php.net/output-handler ;output_handler = +; URL rewriter function rewrites URL on the fly by using +; output buffer. You can set target tags by this configuration. +; "form" tag is special tag. It will add hidden input tag to pass values. +; Refer to session.trans_sid_tags for usage. +; Default Value: "form=" +; Development Value: "form=" +; Production Value: "form=" +;url_rewriter.tags + +; URL rewriter will not rewrite absolute URL nor form by default. To enable +; absolute URL rewrite, allowed hosts must be defined at RUNTIME. +; Refer to session.trans_sid_hosts for more details. +; Default Value: "" +; Development Value: "" +; Production Value: "" +;url_rewriter.hosts + ; Transparent output compression using the zlib library ; Valid values for this option are 'off', 'on', or a specific buffer size ; to be used for compression (default is 4KB) @@ -290,23 +288,34 @@ implicit_flush = Off ; callback-function. unserialize_callback_func = -; When floats & doubles are serialized store serialize_precision significant +; The unserialize_max_depth specifies the default depth limit for unserialized +; structures. Setting the depth limit too high may result in stack overflows +; during unserialization. The unserialize_max_depth ini setting can be +; overridden by the max_depth option on individual unserialize() calls. +; A value of 0 disables the depth limit. +;unserialize_max_depth = 4096 + +; When floats & doubles are serialized, store serialize_precision significant ; digits after the floating point. The default value ensures that when floats ; are decoded with unserialize, the data will remain the same. -serialize_precision = 17 +; The value is also used for json_encode when encoding double values. +; If -1 is used, then dtoa mode 0 is used which automatically select the best +; precision. +serialize_precision = -1 ; open_basedir, if set, limits all file operations to the defined directory ; and below. This directive makes most sense if used in a per-directory ; or per-virtualhost web server configuration file. +; Note: disables the realpath cache ; http://php.net/open-basedir ;open_basedir = -; This directive allows you to disable certain functions for security reasons. +; This directive allows you to disable certain functions. ; It receives a comma-delimited list of function names. ; http://php.net/disable-functions -disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, +disable_functions = -; This directive allows you to disable certain classes for security reasons. +; This directive allows you to disable certain classes. ; It receives a comma-delimited list of class names. ; http://php.net/disable-classes disable_classes = @@ -330,8 +339,9 @@ disable_classes = ; Determines the size of the realpath cache to be used by PHP. This value should ; be increased on systems where PHP opens many files to reflect the quantity of ; the file operations performed. +; Note: if open_basedir is set, the cache is disabled ; http://php.net/realpath-cache-size -;realpath_cache_size = 16k +;realpath_cache_size = 4096k ; Duration of time, in seconds for which to cache realpath information for a given ; file or directory. For systems with rarely changing files, consider increasing this @@ -355,6 +365,12 @@ zend.enable_gc = On ; Default: "" ;zend.script_encoding = +; Allows to include or exclude arguments from stack traces generated for exceptions. +; In production, it is recommended to turn this setting on to prohibit the output +; of sensitive information in stack traces +; Default: Off +zend.exception_ignore_args = On + ;;;;;;;;;;;;;;;;; ; Miscellaneous ; ;;;;;;;;;;;;;;;;; @@ -364,7 +380,7 @@ zend.enable_gc = On ; threat in any way, but it makes it possible to determine whether you use PHP ; on your server or not. ; http://php.net/expose-php -expose_php = Off +expose_php = On ;;;;;;;;;;;;;;;;;;; ; Resource Limits ; @@ -373,10 +389,7 @@ expose_php = Off ; Maximum execution time of each script, in seconds ; http://php.net/max-execution-time ; Note: This directive is hardcoded to 0 for the CLI SAPI -; max_execution_time = 30 - -; BLIS modification! -max_execution_time = 300 +max_execution_time = 30 ; Maximum amount of time each script may spend parsing request data. It's a good ; idea to limit this time on productions servers in order to eliminate unexpectedly @@ -393,17 +406,11 @@ max_input_time = 60 ;max_input_nesting_level = 64 ; How many GET/POST/COOKIE input variables may be accepted -; max_input_vars = 1000 - -; BLIS modification! -max_input_vars = 100000 +;max_input_vars = 1000 -; Maximum amount of memory a script may consume (128MB) +; Maximum amount of memory a script may consume ; http://php.net/memory-limit -; memory_limit = 128M - -; BLIS modification! -memory_limit = 500M +memory_limit = -1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Error handling and logging ; @@ -516,7 +523,7 @@ ignore_repeated_errors = Off ignore_repeated_source = Off ; If this parameter is set to Off, then memory leaks will not be shown (on -; stdout or in the log). This has only effect in a debug compile, and if +; stdout or in the log). This is only effective in a debug compile, and if ; error reporting includes E_WARNING in the allowed list ; http://php.net/report-memleaks report_memleaks = On @@ -527,11 +534,12 @@ report_memleaks = On ; Store the last error/warning message in $php_errormsg (boolean). Setting this value ; to On can assist in debugging and is appropriate for development servers. It should ; however be disabled on production servers. +; This directive is DEPRECATED. ; Default Value: Off -; Development Value: On +; Development Value: Off ; Production Value: Off ; http://php.net/track-errors -track_errors = Off +;track_errors = Off ; Turn off normal error reporting and emit XML-RPC error XML ; http://php.net/xmlrpc-errors @@ -544,11 +552,8 @@ track_errors = Off ; error message as HTML for easier reading. This directive controls whether ; the error message is formatted as HTML or not. ; Note: This directive is hardcoded to Off for the CLI SAPI -; Default Value: On -; Development Value: On -; Production value: On ; http://php.net/html-errors -html_errors = On +;html_errors = On ; If html_errors is set to On *and* docref_root is not empty, then PHP ; produces clickable error messages that direct to a page describing the error @@ -586,6 +591,26 @@ html_errors = On ; Log errors to syslog (Event Log on Windows). ;error_log = syslog +; The syslog ident is a string which is prepended to every message logged +; to syslog. Only used when error_log is set to syslog. +;syslog.ident = php + +; The syslog facility is used to specify what type of program is logging +; the message. Only used when error_log is set to syslog. +;syslog.facility = user + +; Set this to disable filtering control characters (the default). +; Some loggers only accept NVT-ASCII, others accept anything that's not +; control characters. If your logger accepts everything, then no filtering +; is needed at all. +; Allowed values are: +; ascii (all printable ASCII characters and NL) +; no-ctrl (all characters except control characters) +; all (all characters) +; raw (like "all", but messages are not split at newlines) +; http://php.net/syslog.filter +;syslog.filter = ascii + ;windows.show_crt_warning ; Default value: 0 ; Development value: 0 @@ -653,7 +678,7 @@ register_argc_argv = Off ; first used (Just In Time) instead of when the script starts. If these ; variables are not used within a script, having this directive on will result ; in a performance gain. The PHP directive register_argc_argv must be disabled -; for this directive to have any affect. +; for this directive to have any effect. ; http://php.net/auto-globals-jit auto_globals_jit = On @@ -670,10 +695,7 @@ auto_globals_jit = On ; Its value may be 0 to disable the limit. It is ignored if POST data reading ; is disabled through enable_post_data_reading. ; http://php.net/post-max-size -; post_max_size = 8M - -; BLIS modification! -post_max_size = 100M +post_max_size = 8M ; Automatically add files before PHP document. ; http://php.net/auto-prepend-file @@ -710,13 +732,6 @@ default_charset = "UTF-8" ; http://php.net/output-encoding ;output_encoding = -; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is -; to disable this feature and it will be removed in a future version. -; If post reading is disabled through enable_post_data_reading, -; $HTTP_RAW_POST_DATA is *NOT* populated. -; http://php.net/always-populate-raw-post-data -;always_populate_raw_post_data = -1 - ;;;;;;;;;;;;;;;;;;;;;;;;; ; Paths and Directories ; ;;;;;;;;;;;;;;;;;;;;;;;;; @@ -745,13 +760,13 @@ user_dir = ; Directory in which the loadable extensions (modules) reside. ; http://php.net/extension-dir -; extension_dir = "./" +;extension_dir = "./" ; On windows: -; extension_dir = "ext" +;extension_dir = "ext" ; Directory where the temporary files should be placed. ; Defaults to the system default (see sys_get_temp_dir) -; sys_temp_dir = "/tmp" +;sys_temp_dir = "/tmp" ; Whether or not to enable the dl() function. The dl() function does NOT work ; properly in multithreaded servers, such as IIS or Zeus, and is automatically @@ -788,10 +803,9 @@ enable_dl = Off ; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside ; of the web tree and people will not be able to circumvent .htaccess security. -; http://php.net/cgi.dicard-path ;cgi.discard_path=1 -; FastCGI under IIS (on WINNT based OS) supports the ability to impersonate +; FastCGI under IIS supports the ability to impersonate ; security tokens of the calling client. This allows IIS to define the ; security context that the request runs under. mod_fastcgi under Apache ; does not currently support this feature (03/17/2002) @@ -878,65 +892,65 @@ default_socket_timeout = 60 ; If you wish to have an extension loaded automatically, use the following ; syntax: ; -; extension=modulename.extension -; -; For example, on Windows: +; extension=modulename ; -; extension=msql.dll +; For example: ; -; ... or under UNIX: +; extension=mysqli ; -; extension=msql.so +; When the extension library to load is not located in the default extension +; directory, You may specify an absolute path to the library file: ; -; ... or with a path: +; extension=/path/to/extension/mysqli.so ; -; extension=/path/to/extension/msql.so +; Note : The syntax used in previous PHP versions ('extension=.so' and +; 'extension='php_.dll') is supported for legacy reasons and may be +; deprecated in a future PHP major version. So, when it is possible, please +; move to the new ('extension=) syntax. ; -; If you only provide the name of the extension, PHP will look for it in its -; default extension directory. +; Notes for Windows environments : ; -; Windows Extensions -; Note that ODBC support is built in, so no dll is needed for it. -; Note that many DLL files are located in the extensions/ (PHP 4) ext/ (PHP 5) -; extension folders as well as the separate PECL DLL download (PHP 5). -; Be sure to appropriately set the extension_dir directive. +; - Many DLL files are located in the extensions/ (PHP 4) or ext/ (PHP 5+) +; extension folders as well as the separate PECL DLL download (PHP 5+). +; Be sure to appropriately set the extension_dir directive. ; -;extension=php_bz2.dll -;extension=php_curl.dll -;extension=php_fileinfo.dll -;extension=php_gd2.dll -;extension=php_gettext.dll -;extension=php_gmp.dll -;extension=php_intl.dll -;extension=php_imap.dll -;extension=php_interbase.dll -;extension=php_ldap.dll -;extension=php_mbstring.dll -;extension=php_exif.dll ; Must be after mbstring as it depends on it -;extension=php_mysql.dll -;extension=php_mysqli.dll -;extension=php_oci8_12c.dll ; Use with Oracle Database 12c Instant Client -;extension=php_openssl.dll -;extension=php_pdo_firebird.dll -;extension=php_pdo_mysql.dll -;extension=php_pdo_oci.dll -;extension=php_pdo_odbc.dll -;extension=php_pdo_pgsql.dll -;extension=php_pdo_sqlite.dll -;extension=php_pgsql.dll -;extension=php_shmop.dll - -; The MIBS data available in the PHP distribution must be installed. -; See http://www.php.net/manual/en/snmp.installation.php -;extension=php_snmp.dll - -;extension=php_soap.dll -;extension=php_sockets.dll -;extension=php_sqlite3.dll -;extension=php_sybase_ct.dll -;extension=php_tidy.dll -;extension=php_xmlrpc.dll -;extension=php_xsl.dll +;extension=bz2 +;extension=curl +;extension=ffi +;extension=ftp +;extension=fileinfo +;extension=gd2 +;extension=gettext +;extension=gmp +;extension=intl +;extension=imap +;extension=ldap +;extension=mbstring +;extension=exif ; Must be after mbstring as it depends on it +;extension=mysqli +;extension=oci8_12c ; Use with Oracle Database 12c Instant Client +;extension=odbc +;extension=openssl +;extension=pdo_firebird +;extension=pdo_mysql +;extension=pdo_oci +;extension=pdo_odbc +;extension=pdo_pgsql +;extension=pdo_sqlite +;extension=pgsql +;extension=shmop + +; The MIBS data available in the PHP distribution must be installed. +; See http://www.php.net/manual/en/snmp.installation.php +;extension=snmp + +;extension=soap +;extension=sockets +;extension=sodium +;extension=sqlite3 +;extension=tidy +;extension=xmlrpc +;extension=xsl ;;;;;;;;;;;;;;;;;;; ; Module Settings ; @@ -973,7 +987,7 @@ cli_server.color = On [iconv] ; Use of this INI entry is deprecated, use global input_encoding instead. ; If empty, default_charset or input_encoding or iconv.input_encoding is used. -; The precedence is: default_charset < intput_encoding < iconv.input_encoding +; The precedence is: default_charset < input_encoding < iconv.input_encoding ;iconv.input_encoding = ; Use of this INI entry is deprecated, use global internal_encoding instead. @@ -988,6 +1002,13 @@ cli_server.color = On ; otherwise output encoding conversion cannot be performed. ;iconv.output_encoding = +[imap] +; rsh/ssh logins are disabled by default. Use this INI entry if you want to +; enable them. Note that the IMAP library does not filter mailbox names before +; passing them to rsh/ssh command, thus passing untrusted data to this function +; with rsh/ssh enabled is insecure. +;imap.enable_insecure_rsh=0 + [intl] ;intl.default_locale = ; This directive allows you to produce PHP errors when some error @@ -1008,20 +1029,24 @@ cli_server.color = On ; the sqlite_dbpage virtual table. ; https://www.sqlite.org/c3ref/c_dbconfig_defensive.html ; (for older SQLite versions, this flag has no use) -sqlite3.defensive = 1 +;sqlite3.defensive = 1 [Pcre] -;PCRE library backtracking limit. +; PCRE library backtracking limit. ; http://php.net/pcre.backtrack-limit ;pcre.backtrack_limit=100000 -;PCRE library recursion limit. -;Please note that if you set this value to a high number you may consume all -;the available process stack and eventually crash PHP (due to reaching the -;stack size limit imposed by the Operating System). +; PCRE library recursion limit. +; Please note that if you set this value to a high number you may consume all +; the available process stack and eventually crash PHP (due to reaching the +; stack size limit imposed by the Operating System). ; http://php.net/pcre.recursion-limit ;pcre.recursion_limit=100000 +; Enables or disables JIT compilation of patterns. This requires the PCRE +; library to be compiled with JIT support. +;pcre.jit=1 + [Pdo] ; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" ; http://php.net/pdo-odbc.connection-pooling @@ -1030,13 +1055,8 @@ sqlite3.defensive = 1 ;pdo_odbc.db2_instance_name [Pdo_mysql] -; If mysqlnd is used: Number of cache slots for the internal result set cache -; http://php.net/pdo_mysql.cache_size -pdo_mysql.cache_size = 2000 - ; Default socket name for local MySQL connects. If empty, uses the built-in ; MySQL defaults. -; http://php.net/pdo_mysql.default-socket pdo_mysql.default_socket= [Phar] @@ -1069,7 +1089,7 @@ smtp_port = 25 ;mail.force_extra_parameters = ; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename -mail.add_x_header = On +mail.add_x_header = Off ; The path to a log file that will log all mail() calls. Log entries include ; the full path of the script, line number, To address and headers. @@ -1077,10 +1097,6 @@ mail.add_x_header = On ; Log mail to syslog (Event Log on Windows). ;mail.log = syslog -[SQL] -; http://php.net/sql.safe-mode -sql.safe_mode = Off - [ODBC] ; http://php.net/odbc.default-db ;odbc.default_db = Not yet implemented @@ -1122,97 +1138,6 @@ odbc.defaultlrl = 4096 ; http://php.net/odbc.defaultbinmode odbc.defaultbinmode = 1 -;birdstep.max_links = -1 - -[Interbase] -; Allow or prevent persistent links. -ibase.allow_persistent = 1 - -; Maximum number of persistent links. -1 means no limit. -ibase.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -ibase.max_links = -1 - -; Default database name for ibase_connect(). -;ibase.default_db = - -; Default username for ibase_connect(). -;ibase.default_user = - -; Default password for ibase_connect(). -;ibase.default_password = - -; Default charset for ibase_connect(). -;ibase.default_charset = - -; Default timestamp format. -ibase.timestampformat = "%Y-%m-%d %H:%M:%S" - -; Default date format. -ibase.dateformat = "%Y-%m-%d" - -; Default time format. -ibase.timeformat = "%H:%M:%S" - -[MySQL] -; Allow accessing, from PHP's perspective, local files with LOAD DATA statements -; http://php.net/mysql.allow_local_infile -mysql.allow_local_infile = On - -; Allow or prevent persistent links. -; http://php.net/mysql.allow-persistent -mysql.allow_persistent = On - -; If mysqlnd is used: Number of cache slots for the internal result set cache -; http://php.net/mysql.cache_size -mysql.cache_size = 2000 - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/mysql.max-persistent -mysql.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -; http://php.net/mysql.max-links -mysql.max_links = -1 - -; Default port number for mysql_connect(). If unset, mysql_connect() will use -; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the -; compile-time value defined MYSQL_PORT (in that order). Win32 will only look -; at MYSQL_PORT. -; http://php.net/mysql.default-port -mysql.default_port = - -; Default socket name for local MySQL connects. If empty, uses the built-in -; MySQL defaults. -; http://php.net/mysql.default-socket -mysql.default_socket = - -; Default host for mysql_connect() (doesn't apply in safe mode). -; http://php.net/mysql.default-host -mysql.default_host = - -; Default user for mysql_connect() (doesn't apply in safe mode). -; http://php.net/mysql.default-user -mysql.default_user = - -; Default password for mysql_connect() (doesn't apply in safe mode). -; Note that this is generally a *bad* idea to store passwords in this file. -; *Any* user with PHP access can run 'echo get_cfg_var("mysql.default_password") -; and reveal this password! And of course, any users with read access to this -; file will be able to reveal the password as well. -; http://php.net/mysql.default-password -mysql.default_password = - -; Maximum time (in seconds) for connect timeout. -1 means no limit -; http://php.net/mysql.connect-timeout -mysql.connect_timeout = 60 - -; Trace mode. When trace_mode is active (=On), warnings for table/index scans and -; SQL-Errors will be displayed. -; http://php.net/mysql.trace-mode -mysql.trace_mode = Off - [MySQLi] ; Maximum number of persistent links. -1 means no limit. @@ -1231,10 +1156,6 @@ mysqli.allow_persistent = On ; http://php.net/mysqli.max-links mysqli.max_links = -1 -; If mysqlnd is used: Number of cache slots for the internal result set cache -; http://php.net/mysqli.cache_size -mysqli.cache_size = 2000 - ; Default port number for mysqli_connect(). If unset, mysqli_connect() will use ; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the ; compile-time value defined MYSQL_PORT (in that order). Win32 will only look @@ -1247,11 +1168,11 @@ mysqli.default_port = 3306 ; http://php.net/mysqli.default-socket mysqli.default_socket = -; Default host for mysql_connect() (doesn't apply in safe mode). +; Default host for mysqli_connect() (doesn't apply in safe mode). ; http://php.net/mysqli.default-host mysqli.default_host = -; Default user for mysql_connect() (doesn't apply in safe mode). +; Default user for mysqli_connect() (doesn't apply in safe mode). ; http://php.net/mysqli.default-user mysqli.default_user = @@ -1269,12 +1190,10 @@ mysqli.reconnect = Off [mysqlnd] ; Enable / Disable collection of general statistics by mysqlnd which can be ; used to tune and monitor MySQL operations. -; http://php.net/mysqlnd.collect_statistics mysqlnd.collect_statistics = On ; Enable / Disable collection of memory usage statistics by mysqlnd which can be ; used to tune and monitor MySQL operations. -; http://php.net/mysqlnd.collect_memory_statistics mysqlnd.collect_memory_statistics = Off ; Records communication from all extensions using mysqlnd to the specified log @@ -1283,29 +1202,23 @@ mysqlnd.collect_memory_statistics = Off ;mysqlnd.debug = ; Defines which queries will be logged. -; http://php.net/mysqlnd.log_mask ;mysqlnd.log_mask = 0 ; Default size of the mysqlnd memory pool, which is used by result sets. -; http://php.net/mysqlnd.mempool_default_size ;mysqlnd.mempool_default_size = 16000 ; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. -; http://php.net/mysqlnd.net_cmd_buffer_size ;mysqlnd.net_cmd_buffer_size = 2048 ; Size of a pre-allocated buffer used for reading data sent by the server in ; bytes. -; http://php.net/mysqlnd.net_read_buffer_size ;mysqlnd.net_read_buffer_size = 32768 ; Timeout for network requests in seconds. -; http://php.net/mysqlnd.net_read_timeout ;mysqlnd.net_read_timeout = 31536000 ; SHA-256 Authentication Plugin related. File with the MySQL server public RSA ; key. -; http://php.net/mysqlnd.sha256_server_public_key ;mysqlnd.sha256_server_public_key = [OCI8] @@ -1389,45 +1302,6 @@ pgsql.ignore_notice = 0 ; http://php.net/pgsql.log-notice pgsql.log_notice = 0 -[Sybase-CT] -; Allow or prevent persistent links. -; http://php.net/sybct.allow-persistent -sybct.allow_persistent = On - -; Maximum number of persistent links. -1 means no limit. -; http://php.net/sybct.max-persistent -sybct.max_persistent = -1 - -; Maximum number of links (persistent + non-persistent). -1 means no limit. -; http://php.net/sybct.max-links -sybct.max_links = -1 - -; Minimum server message severity to display. -; http://php.net/sybct.min-server-severity -sybct.min_server_severity = 10 - -; Minimum client message severity to display. -; http://php.net/sybct.min-client-severity -sybct.min_client_severity = 10 - -; Set per-context timeout -; http://php.net/sybct.timeout -;sybct.timeout= - -;sybct.packet_size - -; The maximum time in seconds to wait for a connection attempt to succeed before returning failure. -; Default: one minute -;sybct.login_timeout= - -; The name of the host you claim to be connecting from, for display by sp_who. -; Default: none -;sybct.hostname= - -; Allows you to define how often deadlocks are to be retried. -1 means "forever". -; Default: 0 -;sybct.deadlock_retry_count= - [bcmath] ; Number of decimal digits for all bcmath functions. ; http://php.net/bcmath.scale @@ -1472,10 +1346,11 @@ session.save_handler = files ;session.save_path = "/var/lib/php/sessions" ; Whether to use strict session mode. -; Strict session mode does not accept uninitialized session ID and regenerate -; session ID if browser sends uninitialized session ID. Strict mode protects -; applications from session fixation via session adoption vulnerability. It is -; disabled by default for maximum compatibility, but enabling it is encouraged. +; Strict session mode does not accept an uninitialized session ID, and +; regenerates the session ID if the browser sends an uninitialized session ID. +; Strict mode protects applications from session fixation via a session adoption +; vulnerability. It is disabled by default for maximum compatibility, but +; enabling it is encouraged. ; https://wiki.php.net/rfc/strict_sessions session.use_strict_mode = 0 @@ -1513,20 +1388,24 @@ session.cookie_path = / ; http://php.net/session.cookie-domain session.cookie_domain = -; Whether or not to add the httpOnly flag to the cookie, which makes it inaccessible to browser scripting languages such as JavaScript. +; Whether or not to add the httpOnly flag to the cookie, which makes it +; inaccessible to browser scripting languages such as JavaScript. ; http://php.net/session.cookie-httponly session.cookie_httponly = -; Handler used to serialize data. php is the standard serializer of PHP. +; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) +; Current valid values are "Strict", "Lax" or "None". When using "None", +; make sure to include the quotes, as `none` is interpreted like `false` in ini files. +; https://tools.ietf.org/html/draft-west-first-party-cookies-07 +session.cookie_samesite = + +; Handler used to serialize data. php is the standard serializer of PHP. ; http://php.net/session.serialize-handler session.serialize_handler = php -; Defines the probability that the 'garbage collection' process is started -; on every session initialization. The probability is calculated by using -; gc_probability/gc_divisor. Where session.gc_probability is the numerator -; and gc_divisor is the denominator in the equation. Setting this value to 1 -; when the session.gc_divisor value is 100 will give you approximately a 1% chance -; the gc will run on any give request. +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using gc_probability/gc_divisor, +; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. ; Default Value: 1 ; Development Value: 1 ; Production Value: 1 @@ -1534,13 +1413,9 @@ session.serialize_handler = php session.gc_probability = 0 ; Defines the probability that the 'garbage collection' process is started on every -; session initialization. The probability is calculated by using the following equation: -; gc_probability/gc_divisor. Where session.gc_probability is the numerator and -; session.gc_divisor is the denominator in the equation. Setting this value to 1 -; when the session.gc_divisor value is 100 will give you approximately a 1% chance -; the gc will run on any give request. Increasing this value to 1000 will give you -; a 0.1% chance the gc will run on any give request. For high volume production servers, -; this is a more efficient approach. +; session initialization. The probability is calculated by using gc_probability/gc_divisor, +; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. +; For high volume production servers, using a value of 1000 is a more efficient approach. ; Default Value: 100 ; Development Value: 1000 ; Production Value: 1000 @@ -1556,8 +1431,8 @@ session.gc_maxlifetime = 1440 ; (see session.save_path above), then garbage collection does *not* ; happen automatically. You will need to do your own garbage ; collection through a shell script, cron entry, or some other method. -; For example, the following script would is the equivalent of -; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; For example, the following script is the equivalent of setting +; session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): ; find /path/to/sessions -cmin +24 -type f | xargs rm ; Check HTTP Referer to invalidate externally stored URLs containing ids. @@ -1566,19 +1441,6 @@ session.gc_maxlifetime = 1440 ; http://php.net/session.referer-check session.referer_check = -; How many bytes to read from the file. -; http://php.net/session.entropy-length -;session.entropy_length = 32 - -; Specified here to create the session id. -; http://php.net/session.entropy-file -; Defaults to /dev/urandom -; On systems that don't have /dev/urandom but do have /dev/arandom, this will default to /dev/arandom -; If neither are found at compile time, the default is no entropy file. -; On windows, setting the entropy_length setting will activate the -; Windows random source (using the CryptoAPI) -;session.entropy_file = /dev/urandom - ; Set to {nocache,private,public,} to determine HTTP caching aspects ; or leave this empty to avoid sending anti-caching headers. ; http://php.net/session.cache-limiter @@ -1600,15 +1462,39 @@ session.cache_expire = 180 ; http://php.net/session.use-trans-sid session.use_trans_sid = 0 -; Select a hash function for use in generating session ids. -; Possible Values -; 0 (MD5 128 bits) -; 1 (SHA-1 160 bits) -; This option may also be set to the name of any hash function supported by -; the hash extension. A list of available hashes is returned by the hash_algos() -; function. -; http://php.net/session.hash-function -session.hash_function = 0 +; Set session ID character length. This value could be between 22 to 256. +; Shorter length than default is supported only for compatibility reason. +; Users should use 32 or more chars. +; http://php.net/session.sid-length +; Default Value: 32 +; Development Value: 26 +; Production Value: 26 +session.sid_length = 26 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +;
is special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. tag's action attribute URL will not be modified +; unless it is specified. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=" +; Development Value: "a=href,area=href,frame=src,form=" +; Production Value: "a=href,area=href,frame=src,form=" +; http://php.net/url-rewriter.tags +session.trans_sid_tags = "a=href,area=href,frame=src,form=" + +; URL rewriter does not rewrite absolute URLs by default. +; To enable rewrites for absolute paths, target hosts must be specified +; at RUNTIME. i.e. use ini_set() +; tags is special. PHP will check action attribute's URL regardless +; of session.trans_sid_tags setting. +; If no host is defined, HTTP_HOST will be used for allowed host. +; Example value: php.net,www.php.net,wiki.php.net +; Use "," for multiple hosts. No spaces are allowed. +; Default Value: "" +; Development Value: "" +; Production Value: "" +;session.trans_sid_hosts="" ; Define how many bits are stored in each character when converting ; the binary hash data to something readable. @@ -1620,18 +1506,7 @@ session.hash_function = 0 ; Development Value: 5 ; Production Value: 5 ; http://php.net/session.hash-bits-per-character -session.hash_bits_per_character = 5 - -; The URL rewriter will look for URLs in a defined set of HTML tags. -; form/fieldset are special; if you include them here, the rewriter will -; add a hidden field with the info which is otherwise appended -; to URLs. If you want XHTML conformity, remove the form entry. -; Note that all valid entries require a "=", even if no value follows. -; Default Value: "a=href,area=href,frame=src,form=,fieldset=" -; Development Value: "a=href,area=href,frame=src,input=src,form=fakeentry" -; Production Value: "a=href,area=href,frame=src,input=src,form=fakeentry" -; http://php.net/url-rewriter.tags -url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" +session.sid_bits_per_character = 5 ; Enable upload progress tracking in $_SESSION ; Default Value: On @@ -1678,64 +1553,31 @@ url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry" ; http://php.net/session.upload-progress.min-freq ;session.upload_progress.min_freq = "1" -[MSSQL] -; Allow or prevent persistent links. -mssql.allow_persistent = On - -; Maximum number of persistent links. -1 means no limit. -mssql.max_persistent = -1 - -; Maximum number of links (persistent+non persistent). -1 means no limit. -mssql.max_links = -1 - -; Minimum error severity to display. -mssql.min_error_severity = 10 - -; Minimum message severity to display. -mssql.min_message_severity = 10 - -; Compatibility mode with old versions of PHP 3.0. -mssql.compatibility_mode = Off - -; Connect timeout -;mssql.connect_timeout = 5 - -; Query timeout -;mssql.timeout = 60 - -; Valid range 0 - 2147483647. Default = 4096. -;mssql.textlimit = 4096 - -; Valid range 0 - 2147483647. Default = 4096. -;mssql.textsize = 4096 - -; Limits the number of records in each batch. 0 = all records in one batch. -;mssql.batchsize = 0 - -; Specify how datetime and datetim4 columns are returned -; On => Returns data converted to SQL server settings -; Off => Returns values as YYYY-MM-DD hh:mm:ss -;mssql.datetimeconvert = On - -; Use NT authentication when connecting to the server -mssql.secure_connection = Off - -; Specify max number of processes. -1 = library default -; msdlib defaults to 25 -; FreeTDS defaults to 4096 -;mssql.max_procs = -1 - -; Specify client character set. -; If empty or not set the client charset from freetds.conf is used -; This is only used when compiled with FreeTDS -;mssql.charset = "ISO-8859-1" +; Only write session data when session data is changed. Enabled by default. +; http://php.net/session.lazy-write +;session.lazy_write = On [Assertion] +; Switch whether to compile assertions at all (to have no overhead at run-time) +; -1: Do not compile at all +; 0: Jump over assertion at run-time +; 1: Execute assertions +; Changing from or to a negative value is only possible in php.ini! (For turning assertions on and off at run-time, see assert.active, when zend.assertions = 1) +; Default Value: 1 +; Development Value: 1 +; Production Value: -1 +; http://php.net/zend.assertions +zend.assertions = -1 + ; Assert(expr); active by default. ; http://php.net/assert.active ;assert.active = On -; Issue a PHP warning for each failed assertion. +; Throw an AssertionError on failed assertions +; http://php.net/assert.exception +;assert.exception = On + +; Issue a PHP warning for each failed assertion. (Overridden by assert.exception if active) ; http://php.net/assert.warning ;assert.warning = On @@ -1761,7 +1603,7 @@ mssql.secure_connection = Off ; http://php.net/com.allow-dcom ;com.allow_dcom = true -; autoregister constants of a components typlib on com_load() +; autoregister constants of a component's typlib on com_load() ; http://php.net/com.autoregister-typelib ;com.autoregister_typelib = true @@ -1779,7 +1621,7 @@ mssql.secure_connection = Off [mbstring] ; language for internal character representation. -; This affects mb_send_mail() and mbstrig.detect_order. +; This affects mb_send_mail() and mbstring.detect_order. ; http://php.net/mbstring.language ;mbstring.language = Japanese @@ -1792,9 +1634,9 @@ mssql.secure_connection = Off ; Use of this INI entry is deprecated, use global input_encoding instead. ; http input encoding. -; mbstring.encoding_traslation = On is needed to use this setting. +; mbstring.encoding_translation = On is needed to use this setting. ; If empty, default_charset or input_encoding or mbstring.input is used. -; The precedence is: default_charset < intput_encoding < mbsting.http_input +; The precedence is: default_charset < input_encoding < mbstring.http_input ; http://php.net/mbstring.http-input ;mbstring.http_input = @@ -1846,12 +1688,22 @@ mssql.secure_connection = Off ; Default: mbstring.http_output_conv_mimetype=^(text/|application/xhtml\+xml) ;mbstring.http_output_conv_mimetype= +; This directive specifies maximum stack depth for mbstring regular expressions. It is similar +; to the pcre.recursion_limit for PCRE. +; Default: 100000 +;mbstring.regex_stack_limit=100000 + +; This directive specifies maximum retry count for mbstring regular expressions. It is similar +; to the pcre.backtrack_limit for PCRE. +; Default: 1000000 +;mbstring.regex_retry_limit=1000000 + [gd] ; Tell the jpeg decode to ignore warnings and try to create ; a gd image. The warning will then be displayed as notices ; disabled by default ; http://php.net/gd.jpeg-ignore-warning -;gd.jpeg_ignore_warning = 0 +;gd.jpeg_ignore_warning = 1 [exif] ; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. @@ -1913,36 +1765,25 @@ soap.wsdl_cache_limit = 5 ; Sets the maximum number of open links or -1 for unlimited. ldap.max_links = -1 -[mcrypt] -; For more information about mcrypt settings see http://php.net/mcrypt-module-open - -; Directory where to load mcrypt algorithms -; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) -;mcrypt.algorithms_dir= - -; Directory where to load mcrypt modes -; Default: Compiled in into libmcrypt (usually /usr/local/lib/libmcrypt) -;mcrypt.modes_dir= - [dba] ;dba.default_handler= [opcache] ; Determines if Zend OPCache is enabled -;opcache.enable=0 +;opcache.enable=1 ; Determines if Zend OPCache is enabled for the CLI version of PHP ;opcache.enable_cli=0 ; The OPcache shared memory storage size. -;opcache.memory_consumption=64 +;opcache.memory_consumption=128 ; The amount of memory for interned strings in Mbytes. -;opcache.interned_strings_buffer=4 +;opcache.interned_strings_buffer=8 ; The maximum number of keys (scripts) in the OPcache hash table. -; Only numbers between 200 and 100000 are allowed. -;opcache.max_accelerated_files=2000 +; Only numbers between 200 and 1000000 are allowed. +;opcache.max_accelerated_files=10000 ; The maximum percentage of "wasted" memory until a restart is scheduled. ;opcache.max_wasted_percentage=5 @@ -1969,22 +1810,13 @@ ldap.max_links = -1 ; size of the optimized code. ;opcache.save_comments=1 -; If disabled, PHPDoc comments are not loaded from SHM, so "Doc Comments" -; may be always stored (save_comments=1), but not loaded by applications -; that don't need them anyway. -;opcache.load_comments=1 - -; If enabled, a fast shutdown sequence is used for the accelerated code -;opcache.fast_shutdown=0 - ; Allow file existence override (file_exists, etc.) performance feature. ;opcache.enable_file_override=0 ; A bitmask, where each bit enables or disables the appropriate OPcache ; passes -;opcache.optimization_level=0xffffffff +;opcache.optimization_level=0x7FFFBFFF -;opcache.inherited_hack=1 ;opcache.dups_fix=0 ; The location of the OPcache blacklist file (wildcards allowed). @@ -2023,11 +1855,67 @@ ldap.max_links = -1 ; Useful for internal debugging only. ;opcache.protect_memory=0 +; Allows calling OPcache API functions only from PHP scripts which path is +; started from specified string. The default "" means no restriction +;opcache.restrict_api= + +; Mapping base of shared memory segments (for Windows only). All the PHP +; processes have to map shared memory into the same address space. This +; directive allows to manually fix the "Unable to reattach to base address" +; errors. +;opcache.mmap_base= + +; Facilitates multiple OPcache instances per user (for Windows only). All PHP +; processes with the same cache ID and user share an OPcache instance. +;opcache.cache_id= + +; Enables and sets the second level cache directory. +; It should improve performance when SHM memory is full, at server restart or +; SHM reset. The default "" disables file based caching. +;opcache.file_cache= + +; Enables or disables opcode caching in shared memory. +;opcache.file_cache_only=0 + +; Enables or disables checksum validation when script loaded from file cache. +;opcache.file_cache_consistency_checks=1 + +; Implies opcache.file_cache_only=1 for a certain process that failed to +; reattach to the shared memory (for Windows only). Explicitly enabled file +; cache is required. +;opcache.file_cache_fallback=1 + +; Enables or disables copying of PHP code (text segment) into HUGE PAGES. +; This should improve performance, but requires appropriate OS configuration. +;opcache.huge_code_pages=1 + ; Validate cached file permissions. -; opcache.validate_permission=0 +;opcache.validate_permission=0 ; Prevent name collisions in chroot'ed environment. -; opcache.validate_root=0 +;opcache.validate_root=0 + +; If specified, it produces opcode dumps for debugging different stages of +; optimizations. +;opcache.opt_debug_level=0 + +; Specifies a PHP script that is going to be compiled and executed at server +; start-up. +; http://php.net/opcache.preload +;opcache.preload= + +; Preloading code as root is not allowed for security reasons. This directive +; facilitates to let the preloading to be run as another user. +; http://php.net/opcache.preload_user +;opcache.preload_user= + +; Prevents caching files that are less than this number of seconds old. It +; protects from caching of incompletely updated files. In case all file updates +; on your site are atomic, you may increase performance by setting it to "0". +;opcache.file_update_protection=2 + +; Absolute path used to store shared lockfiles (for *nix only). +;opcache.lockfile_path=/tmp [curl] ; A default value for the CURLOPT_CAINFO option. This is required to be an @@ -2052,6 +1940,12 @@ ldap.max_links = -1 ; SSL stream context option. ;openssl.capath= -; Local Variables: -; tab-width: 4 -; End: +[ffi] +; FFI API restriction. Possible values: +; "preload" - enabled in CLI scripts and preloaded files (default) +; "false" - always disabled +; "true" - always enabled +;ffi.enable=preload + +; List of headers files to preload, wildcard patterns allowed. +;ffi.preload= diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 9d1d0813..4cdd71f3 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:noble -ARG PHP_VERSION="5.6" +ARG PHP_VERSION="7.4" ARG MNT_UID=1000 ARG MNT_GID=1000 @@ -16,6 +16,7 @@ RUN echo "Setting ownership of dev directory to $MNT_UID:$MNT_GID" && \ RUN apt-get update && apt-get install -y software-properties-common && \ add-apt-repository ppa:ondrej/php && apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libapache2-mod-php$PHP_VERSION \ apache2 \ curl \ mysql-client \ diff --git a/htdocs/healthcheck.php b/htdocs/healthcheck.php index a77eeb3d..d89b1702 100644 --- a/htdocs/healthcheck.php +++ b/htdocs/healthcheck.php @@ -24,4 +24,4 @@ ?> -{ "status": "" } +{ "status": "" } diff --git a/htdocs/includes/page_elems.php b/htdocs/includes/page_elems.php index 838f8106..f4c50ebf 100644 --- a/htdocs/includes/page_elems.php +++ b/htdocs/includes/page_elems.php @@ -3111,7 +3111,7 @@ public function getSpecimenInfoRow($specimen, $rem_specs, $admin) { ?> Print Barcode - @@ -3243,7 +3243,7 @@ function ConfirmDelete() { ?> - diff --git a/htdocs/regn/new_patient.php b/htdocs/regn/new_patient.php index 17aba61e..eca94f5c 100755 --- a/htdocs/regn/new_patient.php +++ b/htdocs/regn/new_patient.php @@ -31,7 +31,7 @@ '); - } elseif ($format === 'js') { + } elseif ($format === self::FORMAT_JS) { static::writeOutput(static::generateScript()); } static::resetStatic(); } } - public function close() + public function close(): void { self::resetStatic(); } public function reset() { + parent::reset(); + self::resetStatic(); } /** * Forget all logged records */ - public static function resetStatic() + public static function resetStatic(): void { - static::$records = array(); + static::$records = []; } /** * Wrapper for register_shutdown_function to allow overriding */ - protected function registerShutdownFunction() + protected function registerShutdownFunction(): void { if (PHP_SAPI !== 'cli') { - register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); } } /** * Wrapper for echo to allow overriding - * - * @param string $str */ - protected static function writeOutput($str) + protected static function writeOutput(string $str): void { echo $str; } @@ -119,42 +137,55 @@ protected static function writeOutput($str) * If Content-Type is anything else -> unknown * * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* */ - protected static function getResponseFormat() + protected static function getResponseFormat(): string { // Check content type foreach (headers_list() as $header) { if (stripos($header, 'content-type:') === 0) { - // This handler only works with HTML and javascript outputs - // text/javascript is obsolete in favour of application/javascript, but still used - if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { - return 'js'; - } - if (stripos($header, 'text/html') === false) { - return 'unknown'; - } - break; + return static::getResponseFormatFromContentType($header); } } - return 'html'; + return self::FORMAT_HTML; } - private static function generateScript() + /** + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormatFromContentType(string $contentType): string { - $script = array(); + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { + return self::FORMAT_JS; + } + + if (stripos($contentType, 'text/html') !== false) { + return self::FORMAT_HTML; + } + + return self::FORMAT_UNKNOWN; + } + + private static function generateScript(): string + { + $script = []; foreach (static::$records as $record) { $context = static::dump('Context', $record['context']); $extra = static::dump('Extra', $record['extra']); if (empty($context) && empty($extra)) { - $script[] = static::call_array('log', static::handleStyles($record['formatted'])); + $script[] = static::call_array(static::getConsoleMethodForLevel($record['level']), static::handleStyles($record['formatted'])); } else { - $script = array_merge($script, - array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))), + $script = array_merge( + $script, + [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))], $context, $extra, - array(static::call('groupEnd')) + [static::call('groupEnd')] ); } } @@ -162,9 +193,26 @@ private static function generateScript() return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; } - private static function handleStyles($formatted) + private static function getConsoleMethodForLevel(int $level): string + { + return [ + Logger::DEBUG => 'debug', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ][$level] ?? 'log'; + } + + /** + * @return string[] + */ + private static function handleStyles(string $formatted): array { - $args = array(); + $args = []; $format = '%c' . $formatted; preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); @@ -173,7 +221,7 @@ private static function handleStyles($formatted) $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); $pos = $match[0][1]; - $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); } $args[] = static::quote('font-weight: normal'); @@ -182,12 +230,12 @@ private static function handleStyles($formatted) return array_reverse($args); } - private static function handleCustomStyles($style, $string) + private static function handleCustomStyles(string $style, string $string): string { - static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); - static $labels = array(); + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; - return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { if (trim($m[1]) === 'autolabel') { // Format the string as a label with consistent auto assigned background color if (!isset($labels[$string])) { @@ -200,11 +248,22 @@ private static function handleCustomStyles($style, $string) return $m[1]; }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $style; } - private static function dump($title, array $dict) + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array { - $script = array(); + $script = []; $dict = array_filter($dict); if (empty($dict)) { return $script; @@ -215,26 +274,34 @@ private static function dump($title, array $dict) if (empty($value)) { $value = static::quote(''); } - $script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value); + $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value); } return $script; } - private static function quote($arg) + private static function quote(string $arg): string { return '"' . addcslashes($arg, "\"\n\\") . '"'; } - private static function call() + /** + * @param mixed $args + */ + private static function call(...$args): string { - $args = func_get_args(); $method = array_shift($args); + if (!is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } return static::call_array($method, $args); } - private static function call_array($method, array $args) + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string { return 'c.' . $method . '(' . implode(', ', $args) . ');'; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php index 0957e558..fcce5d63 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ -class BufferHandler extends AbstractHandler +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + + /** @var HandlerInterface */ protected $handler; + /** @var int */ protected $bufferSize = 0; + /** @var int */ protected $bufferLimit; + /** @var bool */ protected $flushOnOverflow; - protected $buffer = array(); + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ protected $initialized = false; /** * @param HandlerInterface $handler Handler. * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded */ - public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, $level = Logger::DEBUG, bool $bubble = true, bool $flushOnOverflow = false) { parent::__construct($level, $bubble); $this->handler = $handler; - $this->bufferLimit = (int) $bufferLimit; + $this->bufferLimit = $bufferLimit; $this->flushOnOverflow = $flushOnOverflow; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($record['level'] < $this->level) { return false; @@ -58,7 +66,7 @@ public function handle(array $record) if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors - register_shutdown_function(array($this, 'close')); + register_shutdown_function([$this, 'close']); $this->initialized = true; } @@ -72,9 +80,8 @@ public function handle(array $record) } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->buffer[] = $record; @@ -83,7 +90,7 @@ public function handle(array $record) return false === $this->bubble; } - public function flush() + public function flush(): void { if ($this->bufferSize === 0) { return; @@ -101,20 +108,22 @@ public function __destruct() } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flush(); + + $this->handler->close(); } /** * Clears the buffer without flushing any messages down to the wrapped handler. */ - public function clear() + public function clear(): void { $this->bufferSize = 0; - $this->buffer = array(); + $this->buffer = []; } public function reset() @@ -123,26 +132,36 @@ public function reset() parent::reset(); + $this->resetProcessors(); + if ($this->handler instanceof ResettableInterface) { $this->handler->reset(); } } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->handler->setFormatter($formatter); + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->handler->getFormatter(); + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php index 47120e54..234ecf61 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class ChromePHPHandler extends AbstractProcessingHandler { + use WebRequestRecognizerTrait; + /** * Version of the extension */ - const VERSION = '4.0'; + protected const VERSION = '4.0'; /** * Header name */ - const HEADER_NAME = 'X-ChromeLogger-Data'; + protected const HEADER_NAME = 'X-ChromeLogger-Data'; /** * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) */ - const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + /** @var bool */ protected static $initialized = false; /** @@ -50,19 +56,17 @@ class ChromePHPHandler extends AbstractProcessingHandler */ protected static $overflowed = false; - protected static $json = array( + /** @var mixed[] */ + protected static $json = [ 'version' => self::VERSION, - 'columns' => array('label', 'log', 'backtrace', 'type'), - 'rows' => array(), - ); + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + /** @var bool */ protected static $sendHeaders = true; - /** - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); if (!function_exists('json_encode')) { @@ -71,17 +75,23 @@ public function __construct($level = Logger::DEBUG, $bubble = true) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { - $messages = array(); + if (!$this->isWebRequest()) { + return; + } + + $messages = []; foreach ($records as $record) { if ($record['level'] < $this->level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -94,7 +104,7 @@ public function handleBatch(array $records) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ChromePHPFormatter(); } @@ -104,10 +114,13 @@ protected function getDefaultFormatter() * * @see sendHeader() * @see send() - * @param array $record */ - protected function write(array $record) + protected function write(array $record): void { + if (!$this->isWebRequest()) { + return; + } + self::$json['rows'][] = $record['formatted']; $this->send(); @@ -118,7 +131,7 @@ protected function write(array $record) * * @see sendHeader() */ - protected function send() + protected function send(): void { if (self::$overflowed || !self::$sendHeaders) { return; @@ -132,40 +145,37 @@ protected function send() return; } - self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; } - $json = Utils::jsonEncode(self::$json, null, true); - $data = base64_encode(utf8_encode($json)); + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); if (strlen($data) > 3 * 1024) { self::$overflowed = true; - $record = array( + $record = [ 'message' => 'Incomplete logs, chrome header size limit reached', - 'context' => array(), + 'context' => [], 'level' => Logger::WARNING, 'level_name' => Logger::getLevelName(Logger::WARNING), 'channel' => 'monolog', - 'datetime' => new \DateTime(), - 'extra' => array(), - ); + 'datetime' => new \DateTimeImmutable(), + 'extra' => [], + ]; self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); - $json = Utils::jsonEncode(self::$json, null, true); - $data = base64_encode(utf8_encode($json)); + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); } if (trim($data) !== '') { - $this->sendHeader(self::HEADER_NAME, $data); + $this->sendHeader(static::HEADER_NAME, $data); } } /** * Send header string to the client - * - * @param string $header - * @param string $content */ - protected function sendHeader($header, $content) + protected function sendHeader(string $header, string $content): void { if (!headers_sent() && self::$sendHeaders) { header(sprintf('%s: %s', $header, $content)); @@ -174,39 +184,13 @@ protected function sendHeader($header, $content) /** * Verifies if the headers are accepted by the current user agent - * - * @return bool */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (empty($_SERVER['HTTP_USER_AGENT'])) { return false; } - return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']); - } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php index cc986971..52657613 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -1,4 +1,4 @@ -options = array_merge(array( + $this->options = array_merge([ 'host' => 'localhost', 'port' => 5984, 'dbname' => 'logger', 'username' => null, 'password' => null, - ), $options); + ], $options); parent::__construct($level, $bubble); } @@ -39,7 +44,7 @@ public function __construct(array $options = array(), $level = Logger::DEBUG, $b /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $basicAuth = null; if ($this->options['username']) { @@ -47,17 +52,17 @@ protected function write(array $record) } $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; - $context = stream_context_create(array( - 'http' => array( + $context = stream_context_create([ + 'http' => [ 'method' => 'POST', 'content' => $record['formatted'], 'ignore_errors' => true, 'max_redirects' => 0, 'header' => 'Content-type: application/json', - ), - )); + ], + ]); - if (false === @file_get_contents($url, null, $context)) { + if (false === @file_get_contents($url, false, $context)) { throw new \RuntimeException(sprintf('Could not connect to %s', $url)); } } @@ -65,7 +70,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php index 44928efb..3535a4fc 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -1,4 +1,4 @@ - + * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 */ class CubeHandler extends AbstractProcessingHandler { - private $udpConnection; - private $httpConnection; + /** @var resource|\Socket|null */ + private $udpConnection = null; + /** @var resource|\CurlHandle|null */ + private $httpConnection = null; + /** @var string */ private $scheme; + /** @var string */ private $host; + /** @var int */ private $port; - private $acceptedSchemes = array('http', 'udp'); + /** @var string[] */ + private $acceptedSchemes = ['http', 'udp']; /** * Create a Cube handler @@ -36,23 +43,24 @@ class CubeHandler extends AbstractProcessingHandler * A valid url must consist of three parts : protocol://host:port * Only valid protocols used by Cube are http and udp */ - public function __construct($url, $level = Logger::DEBUG, $bubble = true) + public function __construct(string $url, $level = Logger::DEBUG, bool $bubble = true) { $urlInfo = parse_url($url); - if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); } if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) { throw new \UnexpectedValueException( 'Invalid protocol (' . $urlInfo['scheme'] . ').' - . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); } $this->scheme = $urlInfo['scheme']; $this->host = $urlInfo['host']; - $this->port = $urlInfo['port']; + $this->port = (int) $urlInfo['port']; parent::__construct($level, $bubble); } @@ -63,50 +71,53 @@ public function __construct($url, $level = Logger::DEBUG, $bubble = true) * @throws \LogicException when unable to connect to the socket * @throws MissingExtensionException when there is no socket extension */ - protected function connectUdp() + protected function connectUdp(): void { if (!extension_loaded('sockets')) { throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); } - $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); - if (!$this->udpConnection) { + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { throw new \LogicException('Unable to create a socket'); } + $this->udpConnection = $udpConnection; if (!socket_connect($this->udpConnection, $this->host, $this->port)) { throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); } } /** - * Establish a connection to a http server - * @throws \LogicException when no curl extension + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension */ - protected function connectHttp() + protected function connectHttp(): void { if (!extension_loaded('curl')) { - throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); } - $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); - - if (!$this->httpConnection) { + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); } + $this->httpConnection = $httpConnection; curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $date = $record['datetime']; - $data = array('time' => $date->format('Y-m-d\TH:i:s.uO')); + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; unset($record['datetime']); if (isset($record['context']['type'])) { @@ -126,7 +137,7 @@ protected function write(array $record) } } - private function writeUdp($data) + private function writeUdp(string $data): void { if (!$this->udpConnection) { $this->connectUdp(); @@ -135,17 +146,21 @@ private function writeUdp($data) socket_send($this->udpConnection, $data, strlen($data), 0); } - private function writeHttp($data) + private function writeHttp(string $data): void { if (!$this->httpConnection) { $this->connectHttp(); } + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); - curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Content-Length: ' . strlen('['.$data.']'), - )); + ]); Curl\Util::execute($this->httpConnection, 5, false); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php index 48d30b35..58f63389 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -1,4 +1,4 @@ - */ + private static $retriableErrorCodes = [ CURLE_COULDNT_RESOLVE_HOST, CURLE_COULDNT_CONNECT, CURLE_HTTP_NOT_FOUND, @@ -21,37 +29,43 @@ class Util CURLE_OPERATION_TIMEOUTED, CURLE_HTTP_POST_ERROR, CURLE_SSL_CONNECT_ERROR, - ); + ]; /** * Executes a CURL request with optional retries and exception on failure * - * @param resource $ch curl handler - * @throws \RuntimeException + * @param resource|CurlHandle $ch curl handler + * @param int $retries + * @param bool $closeAfterDone + * @return bool|string @see curl_exec */ - public static function execute($ch, $retries = 5, $closeAfterDone = true) + public static function execute($ch, int $retries = 5, bool $closeAfterDone = true) { while ($retries--) { - if (curl_exec($ch) === false) { + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { $curlErrno = curl_errno($ch); if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) { $curlError = curl_error($ch); - if ($closeAfterDone) { + if (\PHP_VERSION_ID < 80000 && $closeAfterDone) { curl_close($ch); } - throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError)); + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); } continue; } - if ($closeAfterDone) { + if (\PHP_VERSION_ID < 80000 && $closeAfterDone) { curl_close($ch); } - break; + + return $curlResponse; } + + return false; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php index 35b55cb4..9b85ae7e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ class DeduplicationHandler extends BufferHandler { @@ -41,7 +46,7 @@ class DeduplicationHandler extends BufferHandler protected $deduplicationStore; /** - * @var int + * @var Level */ protected $deduplicationLevel; @@ -58,11 +63,13 @@ class DeduplicationHandler extends BufferHandler /** * @param HandlerInterface $handler Handler. * @param string $deduplicationStore The file/path where the deduplication log should be kept - * @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param string|int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::* $deduplicationLevel */ - public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true) + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, int $time = 60, bool $bubble = true) { parent::__construct($handler, 0, Logger::DEBUG, $bubble, false); @@ -71,7 +78,7 @@ public function __construct(HandlerInterface $handler, $deduplicationStore = nul $this->time = $time; } - public function flush() + public function flush(): void { if ($this->bufferSize === 0) { return; @@ -81,7 +88,6 @@ public function flush() foreach ($this->buffer as $record) { if ($record['level'] >= $this->deduplicationLevel) { - $passthru = $passthru || !$this->isDuplicate($record); if ($passthru) { $this->appendRecord($record); @@ -101,7 +107,10 @@ public function flush() } } - private function isDuplicate(array $record) + /** + * @phpstan-param Record $record + */ + private function isDuplicate(array $record): bool { if (!file_exists($this->deduplicationStore)) { return false; @@ -131,21 +140,26 @@ private function isDuplicate(array $record) return false; } - private function collectLogs() + private function collectLogs(): void { if (!file_exists($this->deduplicationStore)) { - return false; + return; } $handle = fopen($this->deduplicationStore, 'rw+'); + + if (!$handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + flock($handle, LOCK_EX); - $validLogs = array(); + $validLogs = []; $timestampValidity = time() - $this->time; while (!feof($handle)) { $log = fgets($handle); - if (substr($log, 0, 10) >= $timestampValidity) { + if ($log && substr($log, 0, 10) >= $timestampValidity) { $validLogs[] = $log; } } @@ -162,7 +176,10 @@ private function collectLogs() $this->gc = false; } - private function appendRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function appendRecord(array $record): void { file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php index b91ffec9..ebd52c3a 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -1,4 +1,4 @@ -client = $client; parent::__construct($level, $bubble); @@ -33,12 +35,12 @@ public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubb /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->client->postDocument($record['formatted']); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php index 8846e0a0..21840bf6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -1,4 +1,4 @@ -=')) { $this->version = 3; $this->marshaler = new Marshaler; @@ -69,9 +65,9 @@ public function __construct(DynamoDbClient $client, $table, $level = Logger::DEB } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $filtered = $this->filterEmptyFields($record['formatted']); if ($this->version === 3) { @@ -81,17 +77,17 @@ protected function write(array $record) $formatted = $this->client->formatAttributes($filtered); } - $this->client->putItem(array( + $this->client->putItem([ 'TableName' => $this->table, 'Item' => $formatted, - )); + ]); } /** - * @param array $record - * @return array + * @param mixed[] $record + * @return mixed[] */ - protected function filterEmptyFields(array $record) + protected function filterEmptyFields(array $record): array { return array_filter($record, function ($value) { return !empty($value) || false === $value || 0 === $value; @@ -99,9 +95,9 @@ protected function filterEmptyFields(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new ScalarFormatter(self::DATE_FORMAT); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php index bb0f83eb..264b380d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.php @@ -1,4 +1,4 @@ -setHosts($hosts) + * ->build(); * - * $client = new \Elastica\Client(); * $options = array( * 'index' => 'elastic_index_name', - * 'type' => 'elastic_doc_type', + * 'type' => 'elastic_doc_type', * ); - * $handler = new ElasticSearchHandler($client, $options); + * $handler = new ElasticsearchHandler($client, $options); * $log = new Logger('application'); * $log->pushHandler($handler); * - * @author Jelle Vink + * @author Avtandil Kikabidze */ -class ElasticSearchHandler extends AbstractProcessingHandler +class ElasticsearchHandler extends AbstractProcessingHandler { /** - * @var Client + * @var Client|Client8 */ protected $client; /** - * @var array Handler config options + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @var bool */ - protected $options = array(); + private $needsType; /** - * @param Client $client Elastica Client object - * @param array $options Handler configuration - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param Client|Client8 $client Elasticsearch Client object + * @param mixed[] $options Handler configuration */ - public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true) + public function __construct($client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) { + if (!$client instanceof Client && !$client instanceof Client8) { + throw new \TypeError('Elasticsearch\Client or Elastic\Elasticsearch\Client instance required'); + } + parent::__construct($level, $bubble); $this->client = $client; $this->options = array_merge( - array( - 'index' => 'monolog', // Elastic index name - 'type' => 'record', // Elastic document type - 'ignore_error' => false, // Suppress Elastica exceptions - ), + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + ], $options ); + + if ($client instanceof Client8 || $client::VERSION[0] === '7') { + $this->needsType = false; + // force the type to _doc for ES8/ES7 + $this->options['type'] = '_doc'; + } else { + $this->needsType = true; + } } /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - $this->bulkSend(array($record['formatted'])); + $this->bulkSend([$record['formatted']]); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - if ($formatter instanceof ElasticaFormatter) { + if ($formatter instanceof ElasticsearchFormatter) { return parent::setFormatter($formatter); } - throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter'); + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); } /** * Getter options - * @return array + * + * @return mixed[] */ - public function getOptions() + public function getOptions(): array { return $this->options; } @@ -96,15 +124,15 @@ public function getOptions() /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new ElasticaFormatter($this->options['index'], $this->options['type']); + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { $documents = $this->getFormatter()->formatBatch($records); $this->bulkSend($documents); @@ -112,17 +140,80 @@ public function handleBatch(array $records) /** * Use Elasticsearch bulk API to send list of documents - * @param array $documents + * + * @param array[] $records Records + _index/_type keys * @throws \RuntimeException */ - protected function bulkSend(array $documents) + protected function bulkSend(array $records): void { try { - $this->client->addDocuments($documents); - } catch (ExceptionInterface $e) { - if (!$this->options['ignore_error']) { - throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + 'index' => $this->needsType ? [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ] : [ + '_index' => $record['_index'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + /** @var Elasticsearch */ + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses($responses): Throwable + { + // @phpstan-ignore offsetAccess.nonOffsetAccessible + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); } + + return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + protected function createExceptionFromError(array $error): Throwable + { + $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); + } + + return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 00000000..fc92ca42 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Logger; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + /** + * @var Client + */ + protected $client; + + /** + * @var mixed[] Handler config options + */ + protected $options = []; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + */ + public function __construct(Client $client, array $options = [], $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->bulkSend([$record['formatted']]); + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php index b2986b0f..f2e22036 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -1,4 +1,4 @@ -expandNewlines) { - $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); - foreach ($lines as $line) { - error_log($line, $this->messageType); - } - } else { + if (!$this->expandNewlines) { error_log((string) $record['formatted'], $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record['formatted']); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 00000000..d4e234ce --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; + +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class FallbackGroupHandler extends GroupHandler +{ + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + /** @var Record[] $records */ + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php index 949f2271..5e43e1dc 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class FilterHandler extends AbstractHandler +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + /** * Handler or factory callable($record, $this) * - * @var callable|\Monolog\Handler\HandlerInterface + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface */ protected $handler; @@ -35,6 +44,7 @@ class FilterHandler extends AbstractHandler * Minimum level for logs that are passed to handler * * @var int[] + * @phpstan-var array */ protected $acceptedLevels; @@ -46,12 +56,17 @@ class FilterHandler extends AbstractHandler protected $bubble; /** + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler). * @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided - * @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param int|string $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ - public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true) + public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, bool $bubble = true) { $this->handler = $handler; $this->bubble = $bubble; @@ -63,9 +78,9 @@ public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel } /** - * @return array + * @phpstan-return array */ - public function getAcceptedLevels() + public function getAcceptedLevels(): array { return array_flip($this->acceptedLevels); } @@ -73,8 +88,11 @@ public function getAcceptedLevels() /** * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided * @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * + * @phpstan-param Level|LevelName|LogLevel::*|array $minLevelOrList + * @phpstan-param Level|LevelName|LogLevel::* $maxLevel */ - public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY) + public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY): self { if (is_array($minLevelOrList)) { $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList); @@ -86,29 +104,30 @@ public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = L })); } $this->acceptedLevels = array_flip($acceptedLevels); + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return isset($this->acceptedLevels[$record['level']]); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->getHandler($record)->handle($record); @@ -117,11 +136,11 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { - $filtered = array(); + $filtered = []; foreach ($records as $record) { if ($this->isHandling($record)) { $filtered[] = $record; @@ -139,11 +158,13 @@ public function handleBatch(array $records) * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @return HandlerInterface + * + * @phpstan-param Record $record */ - public function getHandler(array $record = null) + public function getHandler(?array $record = null) { if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); + $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } @@ -153,20 +174,39 @@ public function getHandler(array $record = null) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->getHandler()->setFormatter($formatter); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->getHandler()->getFormatter(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + public function reset() + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php index aaca12cc..0aa5607b 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ActivationStrategyInterface { /** * Returns whether the given record activates the handler. * - * @param array $record - * @return bool + * @phpstan-param Record $record */ - public function isHandlerActivated(array $record); + public function isHandlerActivated(array $record): bool; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php index 2a2a64d9..7b9abb58 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -1,4 +1,4 @@ - * * @author Mike Meessen + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ChannelLevelActivationStrategy implements ActivationStrategyInterface { + /** + * @var Level + */ private $defaultActionLevel; + + /** + * @var array + */ private $channelToActionLevel; /** - * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any - * @param array $channelToActionLevel An array that maps channel names to action levels. + * @param int|string $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param array $channelToActionLevel + * @phpstan-param Level|LevelName|LogLevel::* $defaultActionLevel */ - public function __construct($defaultActionLevel, $channelToActionLevel = array()) + public function __construct($defaultActionLevel, array $channelToActionLevel = []) { $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel); } - public function isHandlerActivated(array $record) + /** + * @phpstan-param Record $record + */ + public function isHandlerActivated(array $record): bool { if (isset($this->channelToActionLevel[$record['channel']])) { return $record['level'] >= $this->channelToActionLevel[$record['channel']]; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php index 6e630852..5ec88eab 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class ErrorLevelActivationStrategy implements ActivationStrategyInterface { + /** + * @var Level + */ private $actionLevel; + /** + * @param int|string $actionLevel Level or name or value + * + * @phpstan-param Level|LevelName|LogLevel::* $actionLevel + */ public function __construct($actionLevel) { $this->actionLevel = Logger::toMonologLevel($actionLevel); } - public function isHandlerActivated(array $record) + public function isHandlerActivated(array $record): bool { return $record['level'] >= $this->actionLevel; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php index cdabc445..dfcb3af2 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class FingersCrossedHandler extends AbstractHandler +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + + /** + * @var callable|HandlerInterface + * @phpstan-var callable(?Record, HandlerInterface): HandlerInterface|HandlerInterface + */ protected $handler; + /** @var ActivationStrategyInterface */ protected $activationStrategy; + /** @var bool */ protected $buffering = true; + /** @var int */ protected $bufferSize; - protected $buffer = array(); + /** @var Record[] */ + protected $buffer = []; + /** @var bool */ protected $stopBuffering; + /** + * @var ?int + * @phpstan-var ?Level + */ protected $passthruLevel; + /** @var bool */ + protected $bubble; /** - * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). - * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action - * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) - * @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * @psalm-param HandlerInterface|callable(?Record, HandlerInterface): HandlerInterface $handler + * + * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler). + * @param int|string|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int|string $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $passthruLevel + * @phpstan-param Level|LevelName|LogLevel::*|ActivationStrategyInterface $activationStrategy */ - public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null) + public function __construct($handler, $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, $passthruLevel = null) { if (null === $activationStrategy) { $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); @@ -74,9 +105,9 @@ public function __construct($handler, $activationStrategy = null, $bufferSize = } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return true; } @@ -84,24 +115,24 @@ public function isHandling(array $record) /** * Manually activate this logger regardless of the activation strategy */ - public function activate() + public function activate(): void { if ($this->stopBuffering) { $this->buffering = false; } + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); - $this->buffer = array(); + $this->buffer = []; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } if ($this->buffering) { @@ -120,18 +151,20 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flushBuffer(); + + $this->getHandler()->close(); } public function reset() { $this->flushBuffer(); - parent::reset(); + $this->resetProcessors(); if ($this->getHandler() instanceof ResettableInterface) { $this->getHandler()->reset(); @@ -143,16 +176,16 @@ public function reset() * * It also resets the handler to its initial buffering state. */ - public function clear() + public function clear(): void { - $this->buffer = array(); + $this->buffer = []; $this->reset(); } /** * Resets the state of the handler. Stops forwarding records to the wrapped handler. */ - private function flushBuffer() + private function flushBuffer(): void { if (null !== $this->passthruLevel) { $level = $this->passthruLevel; @@ -160,11 +193,11 @@ private function flushBuffer() return $record['level'] >= $level; }); if (count($this->buffer) > 0) { - $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); } } - $this->buffer = array(); + $this->buffer = []; $this->buffering = true; } @@ -174,11 +207,13 @@ private function flushBuffer() * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * * @return HandlerInterface + * + * @phpstan-param Record $record */ - public function getHandler(array $record = null) + public function getHandler(?array $record = null) { if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); + $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } @@ -188,20 +223,30 @@ public function getHandler(array $record = null) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->getHandler()->setFormatter($formatter); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->getHandler()->getFormatter(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php index 2a171bd8..72718de6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FirePHPHandler extends AbstractProcessingHandler { + use WebRequestRecognizerTrait; + /** * WildFire JSON header message format */ - const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; /** * FirePHP structure for parsing messages & their presentation */ - const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; /** * Must reference a "known" plugin, otherwise headers won't display in FirePHP */ - const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; /** * Header prefix for Wildfire to recognize & parse headers */ - const HEADER_PREFIX = 'X-Wf'; + protected const HEADER_PREFIX = 'X-Wf'; /** * Whether or not Wildfire vendor-specific headers have been generated & sent yet + * @var bool */ protected static $initialized = false; @@ -51,35 +57,43 @@ class FirePHPHandler extends AbstractProcessingHandler */ protected static $messageIndex = 1; + /** @var bool */ protected static $sendHeaders = true; /** * Base header creation function used by init headers & record headers * - * @param array $meta Wildfire Plugin, Protocol & Structure Indexes - * @param string $message Log message - * @return array Complete header string ready for the client as key and message as value + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array */ - protected function createHeader(array $meta, $message) + protected function createHeader(array $meta, string $message): array { - $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); - return array($header => $message); + return [$header => $message]; } /** * Creates message header from record * + * @return array + * + * @phpstan-return non-empty-array + * * @see createHeader() - * @param array $record - * @return array + * + * @phpstan-param FormattedRecord $record */ - protected function createRecordHeader(array $record) + protected function createRecordHeader(array $record): array { // Wildfire is extensible to support multiple protocols & plugins in a single request, // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. return $this->createHeader( - array(1, 1, 1, self::$messageIndex++), + [1, 1, 1, self::$messageIndex++], $record['formatted'] ); } @@ -87,7 +101,7 @@ protected function createRecordHeader(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new WildfireFormatter(); } @@ -97,25 +111,23 @@ protected function getDefaultFormatter() * * @see createHeader() * @see sendHeader() - * @return array + * + * @return array */ - protected function getInitHeaders() + protected function getInitHeaders(): array { // Initial payload consists of required headers for Wildfire return array_merge( - $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), - $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), - $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) ); } /** * Send header string to the client - * - * @param string $header - * @param string $content */ - protected function sendHeader($header, $content) + protected function sendHeader(string $header, string $content): void { if (!headers_sent() && self::$sendHeaders) { header(sprintf('%s: %s', $header, $content)); @@ -127,11 +139,10 @@ protected function sendHeader($header, $content) * * @see sendHeader() * @see sendInitHeaders() - * @param array $record */ - protected function write(array $record) + protected function write(array $record): void { - if (!self::$sendHeaders) { + if (!self::$sendHeaders || !$this->isWebRequest()) { return; } @@ -157,10 +168,8 @@ protected function write(array $record) /** * Verifies if the headers are accepted by the current user agent - * - * @return bool */ - protected function headersAccepted() + protected function headersAccepted(): bool { if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { return true; @@ -168,28 +177,4 @@ protected function headersAccepted() return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); } - - /** - * BC getter for the sendHeaders property that has been made static - */ - public function __get($property) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - return static::$sendHeaders; - } - - /** - * BC setter for the sendHeaders property that has been made static - */ - public function __set($property, $value) - { - if ('sendHeaders' !== $property) { - throw new \InvalidArgumentException('Undefined property '.$property); - } - - static::$sendHeaders = $value; - } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php index c43c0134..85c95b9d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class FleepHookHandler extends SocketHandler { - const FLEEP_HOST = 'fleep.io'; + protected const FLEEP_HOST = 'fleep.io'; - const FLEEP_HOOK_URI = '/hook/'; + protected const FLEEP_HOOK_URI = '/hook/'; /** * @var string Webhook token (specifies the conversation where logs are sent) @@ -40,20 +43,35 @@ class FleepHookHandler extends SocketHandler * see https://fleep.io/integrations/webhooks/ * * @param string $token Webhook token - * @param bool|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @throws MissingExtensionException */ - public function __construct($token, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $token, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); } $this->token = $token; - $connectionString = 'ssl://' . self::FLEEP_HOST . ':443'; - parent::__construct($connectionString, $level, $bubble); + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); } /** @@ -63,29 +81,24 @@ public function __construct($token, $level = Logger::DEBUG, $bubble = true) * * @return LineFormatter */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(null, null, true, true); } /** * Handles a log record - * - * @param array $record */ - public function write(array $record) + public function write(array $record): void { parent::write($record); $this->closeSocket(); } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -94,14 +107,11 @@ protected function generateDataStream($record) /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { - $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; - $header .= "Host: " . self::FLEEP_HOST . "\r\n"; + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; $header .= "Content-Length: " . strlen($content) . "\r\n"; $header .= "\r\n"; @@ -112,14 +122,13 @@ private function buildHeader($content) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { - $dataArray = array( + $dataArray = [ 'message' => $record['formatted'], - ); + ]; return http_build_query($dataArray); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php index f0f010cb..5715d580 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -1,4 +1,4 @@ - * @see https://www.flowdock.com/api/push + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 */ class FlowdockHandler extends SocketHandler { @@ -35,26 +38,39 @@ class FlowdockHandler extends SocketHandler protected $apiToken; /** - * @param string $apiToken - * @param bool|int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * * @throws MissingExtensionException if OpenSSL is missing */ - public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $apiToken, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); } - parent::__construct('ssl://api.flowdock.com:443', $level, $bubble); + parent::__construct( + 'ssl://api.flowdock.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->apiToken = $apiToken; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { if (!$formatter instanceof FlowdockFormatter) { throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); @@ -65,20 +81,16 @@ public function setFormatter(FormatterInterface $formatter) /** * Gets the default formatter. - * - * @return FormatterInterface */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { parent::write($record); @@ -86,12 +98,9 @@ protected function write(array $record) } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -101,21 +110,17 @@ protected function generateDataStream($record) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { return Utils::jsonEncode($record['formatted']['flowdock']); } /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; $header .= "Host: api.flowdock.com\r\n"; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php index 3e2f1b28..fc1693cd 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -16,8 +16,6 @@ /** * Interface to describe loggers that have a formatter * - * This interface is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano */ interface FormattableHandlerInterface diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php index e9ec5e77..b60bdce0 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -17,20 +17,17 @@ /** * Helper trait for implementing FormattableInterface * - * This trait is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano */ trait FormattableHandlerTrait { /** - * @var FormatterInterface + * @var ?FormatterInterface */ protected $formatter; /** - * {@inheritdoc} - * @suppress PhanTypeMismatchReturn + * {@inheritDoc} */ public function setFormatter(FormatterInterface $formatter): HandlerInterface { @@ -40,7 +37,7 @@ public function setFormatter(FormatterInterface $formatter): HandlerInterface } /** - * {@inheritdoc} + * {@inheritDoc} */ public function getFormatter(): FormatterInterface { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php index b6cde7c6..4ff26c4c 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -1,4 +1,4 @@ -publisher = $publisher; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->publisher->publish($record['formatted']); } @@ -58,7 +50,7 @@ protected function write(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new GelfMessageFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php index 0d461f9c..3c9dc4b3 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ -class GroupHandler extends AbstractHandler +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface { + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ protected $handlers; + /** @var bool */ + protected $bubble; /** - * @param array $handlers Array of Handlers. - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(array $handlers, $bubble = true) + public function __construct(array $handlers, bool $bubble = true) { foreach ($handlers as $handler) { if (!$handler instanceof HandlerInterface) { @@ -40,9 +47,9 @@ public function __construct(array $handlers, $bubble = true) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -54,14 +61,13 @@ public function isHandling(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { @@ -72,18 +78,16 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { if ($this->processors) { - $processed = array(); + $processed = []; foreach ($records as $record) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - $processed[] = $record; + $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } @@ -94,7 +98,7 @@ public function handleBatch(array $records) public function reset() { - parent::reset(); + $this->resetProcessors(); foreach ($this->handlers as $handler) { if ($handler instanceof ResettableInterface) { @@ -103,13 +107,24 @@ public function reset() } } + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { foreach ($this->handlers as $handler) { - $handler->setFormatter($formatter); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } } return $this; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 00000000..34b4935d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + $reflClass = new \ReflectionClass($this); + + $keys = []; + foreach ($reflClass->getProperties() as $reflProp) { + if (!$reflProp->isStatic()) { + $keys[] = $reflProp->getName(); + } + } + + return $keys; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php index 8d5a4a09..affcc51f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ interface HandlerInterface { @@ -32,8 +33,10 @@ interface HandlerInterface * @param array $record Partial log record containing only a level key * * @return bool + * + * @phpstan-param array{level: Level} $record */ - public function isHandling(array $record); + public function isHandling(array $record): bool; /** * Handles a record. @@ -45,46 +48,38 @@ public function isHandling(array $record); * Unless the bubbling is interrupted (by returning true), the Logger class will keep on * calling further handlers in the stack with a given log record. * - * @param array $record The record to handle - * @return bool true means that this handler handled the record, and that bubbling is not permitted. - * false means the record was either not processed or that this handler allows bubbling. + * @param array $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + * + * @phpstan-param Record $record */ - public function handle(array $record); + public function handle(array $record): bool; /** * Handles a set of records at once. * * @param array $records The records to handle (an array of record arrays) - */ - public function handleBatch(array $records); - - /** - * Adds a processor in the stack. * - * @param callable $callback - * @return self + * @phpstan-param Record[] $records */ - public function pushProcessor($callback); + public function handleBatch(array $records): void; /** - * Removes the processor on top of the stack and returns it. + * Closes the handler. * - * @return callable - */ - public function popProcessor(); - - /** - * Sets the formatter. + * Ends a log cycle and frees all resources used by the handler. * - * @param FormatterInterface $formatter - * @return self - */ - public function setFormatter(FormatterInterface $formatter); - - /** - * Gets the formatter. + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. * - * @return FormatterInterface + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. */ - public function getFormatter(); + public function close(): void; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php index 55e64986..d4351b9f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -1,4 +1,4 @@ - */ -class HandlerWrapper implements HandlerInterface, ResettableInterface +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface { /** * @var HandlerInterface */ protected $handler; - /** - * HandlerWrapper constructor. - * @param HandlerInterface $handler - */ public function __construct(HandlerInterface $handler) { $this->handler = $handler; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->handler->isHandling($record); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { return $this->handler->handle($record); } /** - * {@inheritdoc} + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * {@inheritDoc} */ - public function handleBatch(array $records) + public function close(): void { - return $this->handler->handleBatch($records); + $this->handler->close(); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): HandlerInterface { - $this->handler->pushProcessor($callback); + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); - return $this; + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function popProcessor() + public function popProcessor(): callable { - return $this->handler->popProcessor(); + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->handler->setFormatter($formatter); + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->handler->getFormatter(); + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); } public function reset() { if ($this->handler instanceof ResettableInterface) { - return $this->handler->reset(); + $this->handler->reset(); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php deleted file mode 100644 index 30258e36..00000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php +++ /dev/null @@ -1,367 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Logger; - -/** - * Sends notifications through the hipchat api to a hipchat room - * - * Notes: - * API token - HipChat API token - * Room - HipChat Room Id or name, where messages are sent - * Name - Name used to send the message (from) - * notify - Should the message trigger a notification in the clients - * version - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2) - * - * @author Rafael Dohms - * @see https://www.hipchat.com/docs/api - */ -class HipChatHandler extends SocketHandler -{ - /** - * Use API version 1 - */ - const API_V1 = 'v1'; - - /** - * Use API version v2 - */ - const API_V2 = 'v2'; - - /** - * The maximum allowed length for the name used in the "from" field. - */ - const MAXIMUM_NAME_LENGTH = 15; - - /** - * The maximum allowed length for the message. - */ - const MAXIMUM_MESSAGE_LENGTH = 9500; - - /** - * @var string - */ - private $token; - - /** - * @var string - */ - private $room; - - /** - * @var string - */ - private $name; - - /** - * @var bool - */ - private $notify; - - /** - * @var string - */ - private $format; - - /** - * @var string - */ - private $host; - - /** - * @var string - */ - private $version; - - /** - * @param string $token HipChat API Token - * @param string $room The room that should be alerted of the message (Id or Name) - * @param string $name Name used in the "from" field. - * @param bool $notify Trigger a notification in clients or not - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $useSSL Whether to connect via SSL. - * @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) - * @param string $host The HipChat server hostname. - * @param string $version The HipChat API version (default HipChatHandler::API_V1) - */ - public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1) - { - @trigger_error('The Monolog\Handler\HipChatHandler class is deprecated. You should migrate to Slack and the SlackWebhookHandler / SlackbotHandler, see https://www.atlassian.com/partnerships/slack', E_USER_DEPRECATED); - - if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) { - throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.'); - } - - $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; - parent::__construct($connectionString, $level, $bubble); - - $this->token = $token; - $this->name = $name; - $this->notify = $notify; - $this->room = $room; - $this->format = $format; - $this->host = $host; - $this->version = $version; - } - - /** - * {@inheritdoc} - * - * @param array $record - * @return string - */ - protected function generateDataStream($record) - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - /** - * Builds the body of API call - * - * @param array $record - * @return string - */ - private function buildContent($record) - { - $dataArray = array( - 'notify' => $this->version == self::API_V1 ? - ($this->notify ? 1 : 0) : - ($this->notify ? 'true' : 'false'), - 'message' => $record['formatted'], - 'message_format' => $this->format, - 'color' => $this->getAlertColor($record['level']), - ); - - if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) { - if (function_exists('mb_substr')) { - $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; - } else { - $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]'; - } - } - - // if we are using the legacy API then we need to send some additional information - if ($this->version == self::API_V1) { - $dataArray['room_id'] = $this->room; - } - - // append the sender name if it is set - // always append it if we use the v1 api (it is required in v1) - if ($this->version == self::API_V1 || $this->name !== null) { - $dataArray['from'] = (string) $this->name; - } - - return http_build_query($dataArray); - } - - /** - * Builds the header of the API Call - * - * @param string $content - * @return string - */ - private function buildHeader($content) - { - if ($this->version == self::API_V1) { - $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n"; - } else { - // needed for rooms with special (spaces, etc) characters in the name - $room = rawurlencode($this->room); - $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n"; - } - - $header .= "Host: {$this->host}\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } - - /** - * Assigns a color to each level of log records. - * - * @param int $level - * @return string - */ - protected function getAlertColor($level) - { - switch (true) { - case $level >= Logger::ERROR: - return 'red'; - case $level >= Logger::WARNING: - return 'yellow'; - case $level >= Logger::INFO: - return 'green'; - case $level == Logger::DEBUG: - return 'gray'; - default: - return 'yellow'; - } - } - - /** - * {@inheritdoc} - * - * @param array $record - */ - protected function write(array $record) - { - parent::write($record); - $this->finalizeWrite(); - } - - /** - * Finalizes the request by reading some bytes and then closing the socket - * - * If we do not read some but close the socket too early, hipchat sometimes - * drops the request entirely. - */ - protected function finalizeWrite() - { - $res = $this->getResource(); - if (is_resource($res)) { - @fread($res, 2048); - } - $this->closeSocket(); - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - if (count($records) == 0) { - return true; - } - - $batchRecords = $this->combineRecords($records); - - $handled = false; - foreach ($batchRecords as $batchRecord) { - if ($this->isHandling($batchRecord)) { - $this->write($batchRecord); - $handled = true; - } - } - - if (!$handled) { - return false; - } - - return false === $this->bubble; - } - - /** - * Combines multiple records into one. Error level of the combined record - * will be the highest level from the given records. Datetime will be taken - * from the first record. - * - * @param array $records - * @return array - */ - private function combineRecords(array $records) - { - $batchRecord = null; - $batchRecords = array(); - $messages = array(); - $formattedMessages = array(); - $level = 0; - $levelName = null; - $datetime = null; - - foreach ($records as $record) { - $record = $this->processRecord($record); - - if ($record['level'] > $level) { - $level = $record['level']; - $levelName = $record['level_name']; - } - - if (null === $datetime) { - $datetime = $record['datetime']; - } - - $messages[] = $record['message']; - $messageStr = implode(PHP_EOL, $messages); - $formattedMessages[] = $this->getFormatter()->format($record); - $formattedMessageStr = implode('', $formattedMessages); - - $batchRecord = array( - 'message' => $messageStr, - 'formatted' => $formattedMessageStr, - 'context' => array(), - 'extra' => array(), - ); - - if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { - // Pop the last message and implode the remaining messages - $lastMessage = array_pop($messages); - $lastFormattedMessage = array_pop($formattedMessages); - $batchRecord['message'] = implode(PHP_EOL, $messages); - $batchRecord['formatted'] = implode('', $formattedMessages); - - $batchRecords[] = $batchRecord; - $messages = array($lastMessage); - $formattedMessages = array($lastFormattedMessage); - - $batchRecord = null; - } - } - - if (null !== $batchRecord) { - $batchRecords[] = $batchRecord; - } - - // Set the max level and datetime for all records - foreach ($batchRecords as &$batchRecord) { - $batchRecord = array_merge( - $batchRecord, - array( - 'level' => $level, - 'level_name' => $levelName, - 'datetime' => $datetime, - ) - ); - } - - return $batchRecords; - } - - /** - * Validates the length of a string. - * - * If the `mb_strlen()` function is available, it will use that, as HipChat - * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. - * - * Note that this might cause false failures in the specific case of using - * a valid name with less than 16 characters, but 16 or more bytes, on a - * system where `mb_strlen()` is unavailable. - * - * @param string $str - * @param int $length - * - * @return bool - */ - private function validateStringLength($str, $length) - { - if (function_exists('mb_strlen')) { - return (mb_strlen($str) <= $length); - } - - return (strlen($str) <= $length); - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php index f4d3b97e..000ccea4 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -1,4 +1,4 @@ -eventName = $eventName; $this->secretKey = $secretKey; @@ -45,15 +49,15 @@ public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bub } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function write(array $record) + public function write(array $record): void { - $postData = array( + $postData = [ "value1" => $record["channel"], "value2" => $record["level_name"], "value3" => $record["message"], - ); + ]; $postString = Utils::jsonEncode($postData); $ch = curl_init(); @@ -61,9 +65,9 @@ public function write(array $record) curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( + curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", - )); + ]); Curl\Util::execute($ch); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php index 8f683dce..71f64a26 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -1,4 +1,4 @@ -logToken = $token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { return $this->logToken . ' ' . $record['formatted']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php index ea89fb3e..25fcd159 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -1,4 +1,4 @@ -logToken = $token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { return $this->logToken . ' ' . $record['formatted']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php index bcd62e1c..6d13db37 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -1,4 +1,4 @@ -token = $token; @@ -42,26 +59,72 @@ public function __construct($token, $level = Logger::DEBUG, $bubble = true) parent::__construct($level, $bubble); } - public function setTag($tag) + /** + * Loads and returns the shared curl handler for the given endpoint. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + protected function getCurlHandler(string $endpoint) { - $tag = !empty($tag) ? $tag : array(); - $this->tag = is_array($tag) ? $tag : array($tag); + if (!array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; } - public function addTag($tag) + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + * + * @param string $endpoint + * + * @return resource|CurlHandle + */ + private function loadCurlHandle(string $endpoint) + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + */ + public function setTag($tag): self + { + $tag = !empty($tag) ? $tag : []; + $this->tag = is_array($tag) ? $tag : [$tag]; + + return $this; + } + + /** + * @param string[]|string $tag + */ + public function addTag($tag): self { if (!empty($tag)) { - $tag = is_array($tag) ? $tag : array($tag); + $tag = is_array($tag) ? $tag : [$tag]; $this->tag = array_unique(array_merge($this->tag, $tag)); } + + return $this; } - protected function write(array $record) + protected function write(array $record): void { - $this->send($record["formatted"], self::ENDPOINT_SINGLE); + $this->send($record["formatted"], static::ENDPOINT_SINGLE); } - public function handleBatch(array $records) + public function handleBatch(array $records): void { $level = $this->level; @@ -70,32 +133,27 @@ public function handleBatch(array $records) }); if ($records) { - $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH); + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); } } - protected function send($data, $endpoint) + protected function send(string $data, string $endpoint): void { - $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token); + $ch = $this->getCurlHandler($endpoint); - $headers = array('Content-Type: application/json'); + $headers = ['Content-Type: application/json']; if (!empty($this->tag)) { $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); } - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - Curl\Util::execute($ch); + Curl\Util::execute($ch, 5, false); } - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LogglyFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 00000000..859a4690 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + /** + * @var string + */ + private $logToken; + + /** + * @var string + */ + private $hostname; + + /** + * @var string + */ + private $appname; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appname Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $hostname = '', + string $appname = '', + bool $useSSL = true, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appname = $appname; + } + + /** + * {@inheritDoc} + */ + protected function generateDataStream(array $record): string + { + return $this->logToken . ' ' . $record['formatted']; + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if (!empty($this->hostname)) { + $formatter->setHostname($this->hostname); + } + if (!empty($this->appname)) { + $formatter->setAppname($this->appname); + } + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php index 9e232838..97f34320 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -1,4 +1,4 @@ -level) { continue; } - $messages[] = $this->processRecord($record); + /** @var Record $message */ + $message = $this->processRecord($record); + $messages[] = $message; } if (!empty($messages)) { @@ -42,18 +49,24 @@ public function handleBatch(array $records) * * @param string $content formatted email body to be sent * @param array $records the array of log records that formed this content + * + * @phpstan-param Record[] $records */ - abstract protected function send($content, array $records); + abstract protected function send(string $content, array $records): void; /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - $this->send((string) $record['formatted'], array($record)); + $this->send((string) $record['formatted'], [$record]); } - protected function getHighestRecord(array $records) + /** + * @phpstan-param non-empty-array $records + * @phpstan-return Record + */ + protected function getHighestRecord(array $records): array { $highestRecord = null; foreach ($records as $record) { @@ -64,4 +77,19 @@ protected function getHighestRecord(array $records) return $highestRecord; } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php index de039a86..3003500e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -1,4 +1,4 @@ -message = $message; @@ -44,15 +48,22 @@ public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + $message = clone $this->message; - $message->setBody($content); - if (version_compare(\Swift::VERSION, '6.0.0', '>=')) { + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { + /** @phpstan-ignore-next-line */ $message->setDate(time()); } @@ -61,11 +72,11 @@ protected function send($content, array $records) curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array( + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ 'key' => $this->apiKey, 'raw_message' => (string) $message, 'async' => false, - ))); + ])); Curl\Util::execute($ch); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php index 4724a7e2..3965aeea 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -1,4 +1,4 @@ - + * @author Christian Bergau */ class MissingExtensionException extends \Exception { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php index 56fe755b..150efeea 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -1,4 +1,4 @@ -pushHandler($mongodb); * - * @author Thomas Tourlourat + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. */ class MongoDBHandler extends AbstractProcessingHandler { - protected $mongoCollection; + /** @var Collection */ + private $collection; + /** @var Client|Manager */ + private $manager; + /** @var string|null */ + private $namespace = null; - public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct($mongodb, string $database, string $collection, $level = Logger::DEBUG, bool $bubble = true) { - if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) { - throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required'); + if (!($mongodb instanceof Client || $mongodb instanceof Manager)) { + throw new \InvalidArgumentException('MongoDB\Client or MongoDB\Driver\Manager instance required'); } - $this->mongoCollection = $mongo->selectCollection($database, $collection); + if ($mongodb instanceof Client) { + $this->collection = method_exists($mongodb, 'getCollection') ? $mongodb->getCollection($database, $collection) : $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } parent::__construct($level, $bubble); } - protected function write(array $record) + protected function write(array $record): void { - if ($this->mongoCollection instanceof \MongoDB\Collection) { - $this->mongoCollection->insertOne($record["formatted"]); - } else { - $this->mongoCollection->save($record["formatted"]); + if (isset($this->collection)) { + $this->collection->insertOne($record['formatted']); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record["formatted"]); + $this->manager->executeBulkWrite($this->namespace, $bulk); } } /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { - return new NormalizerFormatter(); + return new MongoDBFormatter; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php index d7807fd1..0c0a3bdb 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -1,4 +1,4 @@ -to = is_array($to) ? $to : array($to); + $this->to = (array) $to; $this->subject = $subject; $this->addHeader(sprintf('From: %s', $from)); $this->maxColumnWidth = $maxColumnWidth; @@ -84,10 +82,9 @@ public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubbl /** * Add headers to the message * - * @param string|array $headers Custom added headers - * @return self + * @param string|string[] $headers Custom added headers */ - public function addHeader($headers) + public function addHeader($headers): self { foreach ((array) $headers as $header) { if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { @@ -102,10 +99,9 @@ public function addHeader($headers) /** * Add parameters to the message * - * @param string|array $parameters Custom added parameters - * @return self + * @param string|string[] $parameters Custom added parameters */ - public function addParameter($parameters) + public function addParameter($parameters): self { $this->parameters = array_merge($this->parameters, (array) $parameters); @@ -113,14 +109,19 @@ public function addParameter($parameters) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { - $content = wordwrap($content, $this->maxColumnWidth); + $contentType = $this->getContentType() ?: ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); - $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n"; - if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { $headers .= 'MIME-Version: 1.0' . "\r\n"; } @@ -136,28 +137,20 @@ protected function send($content, array $records) } } - /** - * @return string $contentType - */ - public function getContentType() + public function getContentType(): ?string { return $this->contentType; } - /** - * @return string $encoding - */ - public function getEncoding() + public function getEncoding(): string { return $this->encoding; } /** - * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML - * messages. - * @return self + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. */ - public function setContentType($contentType) + public function setContentType(string $contentType): self { if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); @@ -168,11 +161,7 @@ public function setContentType($contentType) return $this; } - /** - * @param string $encoding - * @return self - */ - public function setEncoding($encoding) + public function setEncoding(string $encoding): self { if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php index 64dc1381..114d749e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -1,4 +1,4 @@ -isNewRelicEnabled()) { throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); @@ -87,7 +88,7 @@ protected function write(array $record) unset($record['formatted']['context']['transaction_name']); } - if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { newrelic_notice_error($record['message'], $record['context']['exception']); unset($record['formatted']['context']['exception']); } else { @@ -124,7 +125,7 @@ protected function write(array $record) * * @return bool */ - protected function isNewRelicEnabled() + protected function isNewRelicEnabled(): bool { return extension_loaded('newrelic'); } @@ -133,10 +134,9 @@ protected function isNewRelicEnabled() * Returns the appname where this log should be sent. Each log can override the default appname, set in this * handler's constructor, by providing the appname in it's context. * - * @param array $context - * @return null|string + * @param mixed[] $context */ - protected function getAppName(array $context) + protected function getAppName(array $context): ?string { if (isset($context['appname'])) { return $context['appname']; @@ -149,11 +149,9 @@ protected function getAppName(array $context) * Returns the name of the current transaction. Each log can override the default transaction name, set in this * handler's constructor, by providing the transaction_name in it's context * - * @param array $context - * - * @return null|string + * @param mixed[] $context */ - protected function getTransactionName(array $context) + protected function getTransactionName(array $context): ?string { if (isset($context['transaction_name'])) { return $context['transaction_name']; @@ -164,20 +162,16 @@ protected function getTransactionName(array $context) /** * Sets the NewRelic application that should receive this log. - * - * @param string $appName */ - protected function setNewRelicAppName($appName) + protected function setNewRelicAppName(string $appName): void { newrelic_set_appname($appName); } /** * Overwrites the name of the current transaction - * - * @param string $transactionName */ - protected function setNewRelicTransactionName($transactionName) + protected function setNewRelicTransactionName(string $transactionName): void { newrelic_name_transaction($transactionName); } @@ -186,7 +180,7 @@ protected function setNewRelicTransactionName($transactionName) * @param string $key * @param mixed $value */ - protected function setNewRelicParameter($key, $value) + protected function setNewRelicParameter(string $key, $value): void { if (null === $value || is_scalar($value)) { newrelic_add_custom_parameter($key, $value); @@ -198,7 +192,7 @@ protected function setNewRelicParameter($key, $value) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 00000000..1ddf0beb --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * {@inheritDoc} + */ + public function isHandling(array $record): bool + { + return true; + } + + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return false; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php index 4b845883..e75ee0c6 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ -class NullHandler extends AbstractHandler +class NullHandler extends Handler { /** - * @param int $level The minimum logging level at which this handler will be triggered + * @var int + */ + private $level; + + /** + * @param string|int $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ public function __construct($level = Logger::DEBUG) { - parent::__construct($level, false); + $this->level = Logger::toMonologLevel($level); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function isHandling(array $record): bool { - if ($record['level'] < $this->level) { - return false; - } + return $record['level'] >= $this->level; + } - return true; + /** + * {@inheritDoc} + */ + public function handle(array $record): bool + { + return $record['level'] >= $this->level; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 00000000..22068c9a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Logger::WARNING => 10, Logger::ERROR => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** @var HandlerInterface */ + private $handler; + + /** @var int[] */ + private $thresholdMap = [ + Logger::DEBUG => 0, + Logger::INFO => 0, + Logger::NOTICE => 0, + Logger::WARNING => 0, + Logger::ERROR => 0, + Logger::CRITICAL => 0, + Logger::ALERT => 0, + Logger::EMERGENCY => 0, + ]; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private $buffer = []; + + /** + * @param HandlerInterface $handler + * @param int[] $thresholdMap Dictionary of logger level => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Logger::DEBUG, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * {@inheritDoc} + */ + public function handle(array $record): bool + { + if ($record['level'] < $this->level) { + return false; + } + + $level = $record['level']; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] == 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * {@inheritDoc} + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * {@inheritDoc} + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php index d0a8b43e..23a1d117 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -1,4 +1,4 @@ -addDebug('SELECT * FROM users', array('db', 'time' => 0.012)); + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); * PC::debug($_SERVER); // PHP Console debugger for any type of vars * * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + * + * @phpstan-import-type Record from \Monolog\Logger + * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 */ class PHPConsoleHandler extends AbstractProcessingHandler { - private $options = array( + /** @var array */ + private $options = [ 'enabled' => true, // bool Is PHP Console server enabled - 'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with... - 'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled 'useOwnErrorsHandler' => false, // bool Enable errors handling 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths @@ -52,7 +56,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'headersLimit' => null, // int|null Set headers size limit for your web-server 'password' => null, // string|null Protect PHP Console connection by password 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed - 'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level @@ -60,40 +64,43 @@ class PHPConsoleHandler extends AbstractProcessingHandler 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug - 'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) - ); + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ]; /** @var Connector */ private $connector; /** - * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details - * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) - * @param int $level - * @param bool $bubble - * @throws Exception + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException */ - public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true) + public function __construct(array $options = [], ?Connector $connector = null, $level = Logger::DEBUG, bool $bubble = true) { if (!class_exists('PhpConsole\Connector')) { - throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); } parent::__construct($level, $bubble); $this->options = $this->initOptions($options); $this->connector = $this->initConnector($connector); } - private function initOptions(array $options) + /** + * @param array $options + * + * @return array + */ + private function initOptions(array $options): array { $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); if ($wrongOptions) { - throw new Exception('Unknown options: ' . implode(', ', $wrongOptions)); + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); } return array_replace($this->options, $options); } - private function initConnector(Connector $connector = null) + private function initConnector(?Connector $connector = null): Connector { if (!$connector) { if ($this->options['dataStorage']) { @@ -108,7 +115,7 @@ private function initConnector(Connector $connector = null) if ($this->options['enabled'] && $connector->isActiveClient()) { if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { - $handler = Handler::getInstance(); + $handler = VendorPhpConsoleHandler::getInstance(); $handler->setHandleErrors($this->options['useOwnErrorsHandler']); $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); $handler->start(); @@ -148,17 +155,20 @@ private function initConnector(Connector $connector = null) return $connector; } - public function getConnector() + public function getConnector(): Connector { return $this->connector; } - public function getOptions() + /** + * @return array + */ + public function getOptions(): array { return $this->options; } - public function handle(array $record) + public function handle(array $record): bool { if ($this->options['enabled'] && $this->connector->isActiveClient()) { return parent::handle($record); @@ -169,22 +179,22 @@ public function handle(array $record) /** * Writes the record down to the log of the implementing handler - * - * @param array $record - * @return void */ - protected function write(array $record) + protected function write(array $record): void { if ($record['level'] < Logger::NOTICE) { $this->handleDebugRecord($record); - } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) { + } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { $this->handleExceptionRecord($record); } else { $this->handleErrorRecord($record); } } - private function handleDebugRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleDebugRecord(array $record): void { $tags = $this->getRecordTags($record); $message = $record['message']; @@ -194,24 +204,34 @@ private function handleDebugRecord(array $record) $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); } - private function handleExceptionRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleExceptionRecord(array $record): void { $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']); } - private function handleErrorRecord(array $record) + /** + * @phpstan-param Record $record + */ + private function handleErrorRecord(array $record): void { $context = $record['context']; $this->connector->getErrorsDispatcher()->dispatchError( - isset($context['code']) ? $context['code'] : null, - isset($context['message']) ? $context['message'] : $record['message'], - isset($context['file']) ? $context['file'] : null, - isset($context['line']) ? $context['line'] : null, + $context['code'] ?? null, + $context['message'] ?? $record['message'], + $context['file'] ?? null, + $context['line'] ?? null, $this->options['classesPartialsTraceIgnore'] ); } + /** + * @phpstan-param Record $record + * @return string + */ private function getRecordTags(array &$record) { $tags = null; @@ -236,7 +256,7 @@ private function getRecordTags(array &$record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter('%message%'); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 00000000..8a8cf1be --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * 
+ * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + /** + * @var string + */ + private $command; + + /** + * @var string|null + */ + private $cwd; + + /** + * @var resource[] + */ + private $pipes = []; + + /** + * @var array + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, $level = Logger::DEBUG, bool $bubble = true, ?string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(array $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record['formatted']); + + $errors = $this->readProcessErrors(); + if (empty($errors) === false) { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || empty($errors) === false) { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php index 66a3d83a..3adec7a4 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -16,25 +16,29 @@ /** * Interface to describe loggers that have processors * - * This interface is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ProcessableHandlerInterface { /** * Adds a processor in the stack. * + * @psalm-param ProcessorInterface|callable(Record): Record $callback + * * @param ProcessorInterface|callable $callback * @return HandlerInterface self */ - public function pushProcessor($callback): HandlerInterface; + public function pushProcessor(callable $callback): HandlerInterface; /** * Removes the processor on top of the stack and returns it. * - * @throws \LogicException In case the processor stack is empty - * @return callable + * @psalm-return ProcessorInterface|callable(Record): Record $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface */ public function popProcessor(): callable; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php index 09f32a12..9ef6e301 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -12,26 +12,27 @@ namespace Monolog\Handler; use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; /** * Helper trait for implementing ProcessableInterface * - * This trait is present in monolog 1.x to ease forward compatibility. - * * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger */ trait ProcessableHandlerTrait { /** * @var callable[] + * @phpstan-var array */ protected $processors = []; /** - * {@inheritdoc} - * @suppress PhanTypeMismatchReturn + * {@inheritDoc} */ - public function pushProcessor($callback): HandlerInterface + public function pushProcessor(callable $callback): HandlerInterface { array_unshift($this->processors, $callback); @@ -39,7 +40,7 @@ public function pushProcessor($callback): HandlerInterface } /** - * {@inheritdoc} + * {@inheritDoc} */ public function popProcessor(): callable { @@ -52,6 +53,9 @@ public function popProcessor(): callable /** * Processes a record. + * + * @phpstan-param Record $record + * @phpstan-return Record */ protected function processRecord(array $record): array { diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php index a99e6ab7..36e19ccc 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -1,4 +1,4 @@ - */ -class PsrHandler extends AbstractHandler +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface { /** * PSR-3 compliant logger @@ -28,12 +33,15 @@ class PsrHandler extends AbstractHandler */ protected $logger; + /** + * @var FormatterInterface|null + */ + protected $formatter; + /** * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true) + public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, bool $bubble = true) { parent::__construct($level, $bubble); @@ -43,14 +51,45 @@ public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bu /** * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if (!$this->isHandling($record)) { return false; } - $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + if ($this->formatter) { + $formatted = $this->formatter->format($record); + $this->logger->log(strtolower($record['level_name']), (string) $formatted, $record['context']); + } else { + $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']); + } return false === $this->bubble; } + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(): FormatterInterface + { + if (!$this->formatter) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php index f27bb3da..fed2303d 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -1,4 +1,4 @@ - * @see https://www.pushover.net/api + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class PushoverHandler extends SocketHandler { + /** @var string */ private $token; + /** @var array */ private $users; + /** @var string */ private $title; - private $user; + /** @var string|int|null */ + private $user = null; + /** @var int */ private $retry; + /** @var int */ private $expire; + /** @var int */ private $highPriorityLevel; + /** @var int */ private $emergencyLevel; + /** @var bool */ private $useFormattedMessage = false; /** * All parameters that can be sent to Pushover * @see https://pushover.net/api - * @var array + * @var array */ - private $parameterNames = array( + private $parameterNames = [ 'token' => true, 'user' => true, 'message' => true, @@ -51,72 +66,103 @@ class PushoverHandler extends SocketHandler 'retry' => true, 'expire' => true, 'callback' => true, - ); + ]; /** * Sounds the api supports by default * @see https://pushover.net/api#sounds - * @var array + * @var string[] */ - private $sounds = array( + private $sounds = [ 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', 'persistent', 'echo', 'updown', 'none', - ); + ]; /** * @param string $token Pushover api token * @param string|array $users Pushover user id or array of ids the message will be sent to - * @param string $title Title sent to the Pushover API - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string|null $title Title sent to the Pushover API * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not * the pushover.net app owner. OpenSSL is required for this option. - * @param int $highPriorityLevel The minimum logging level at which this handler will start + * @param string|int $highPriorityLevel The minimum logging level at which this handler will start * sending "high priority" requests to the Pushover API - * @param int $emergencyLevel The minimum logging level at which this handler will start + * @param string|int $emergencyLevel The minimum logging level at which this handler will start * sending "emergency" requests to the Pushover API - * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. - * @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @phpstan-param string|array $users + * @phpstan-param Level|LevelName|LogLevel::* $highPriorityLevel + * @phpstan-param Level|LevelName|LogLevel::* $emergencyLevel */ - public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) - { + public function __construct( + string $token, + $users, + ?string $title = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useSSL = true, + $highPriorityLevel = Logger::CRITICAL, + $emergencyLevel = Logger::EMERGENCY, + int $retry = 30, + int $expire = 25200, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; - parent::__construct($connectionString, $level, $bubble); + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->token = $token; $this->users = (array) $users; - $this->title = $title ?: gethostname(); + $this->title = $title ?: (string) gethostname(); $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); $this->retry = $retry; $this->expire = $expire; } - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); return $this->buildHeader($content) . $content; } - private function buildContent($record) + /** + * @phpstan-param FormattedRecord $record + */ + private function buildContent(array $record): string { // Pushover has a limit of 512 characters on title and message combined. $maxMessageLength = 512 - strlen($this->title); $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message']; - $message = substr($message, 0, $maxMessageLength); + $message = Utils::substr($message, 0, $maxMessageLength); $timestamp = $record['datetime']->getTimestamp(); - $dataArray = array( + $dataArray = [ 'token' => $this->token, 'user' => $this->user, 'message' => $message, 'title' => $this->title, 'timestamp' => $timestamp, - ); + ]; if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) { $dataArray['priority'] = 2; @@ -141,7 +187,7 @@ private function buildContent($record) return http_build_query($dataArray); } - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /1/messages.json HTTP/1.1\r\n"; $header .= "Host: api.pushover.net\r\n"; @@ -152,7 +198,7 @@ private function buildHeader($content) return $header; } - protected function write(array $record) + protected function write(array $record): void { foreach ($this->users as $user) { $this->user = $user; @@ -164,22 +210,37 @@ protected function write(array $record) $this->user = null; } - public function setHighPriorityLevel($value) + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setHighPriorityLevel($value): self { - $this->highPriorityLevel = $value; + $this->highPriorityLevel = Logger::toMonologLevel($value); + + return $this; } - public function setEmergencyLevel($value) + /** + * @param int|string $value + * + * @phpstan-param Level|LevelName|LogLevel::* $value + */ + public function setEmergencyLevel($value): self { - $this->emergencyLevel = $value; + $this->emergencyLevel = Logger::toMonologLevel($value); + + return $this; } /** * Use the formatted message? - * @param bool $value */ - public function useFormattedMessage($value) + public function useFormattedMessage(bool $value): self { - $this->useFormattedMessage = (bool) $value; + $this->useFormattedMessage = $value; + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php deleted file mode 100644 index b0298fa6..00000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php +++ /dev/null @@ -1,234 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Logger; -use Raven_Client; - -/** - * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server - * using sentry-php (https://github.com/getsentry/sentry-php) - * - * @author Marc Abramowitz - */ -class RavenHandler extends AbstractProcessingHandler -{ - /** - * Translates Monolog log levels to Raven log levels. - */ - protected $logLevels = array( - Logger::DEBUG => Raven_Client::DEBUG, - Logger::INFO => Raven_Client::INFO, - Logger::NOTICE => Raven_Client::INFO, - Logger::WARNING => Raven_Client::WARNING, - Logger::ERROR => Raven_Client::ERROR, - Logger::CRITICAL => Raven_Client::FATAL, - Logger::ALERT => Raven_Client::FATAL, - Logger::EMERGENCY => Raven_Client::FATAL, - ); - - /** - * @var string should represent the current version of the calling - * software. Can be any string (git commit, version number) - */ - protected $release; - - /** - * @var Raven_Client the client object that sends the message to the server - */ - protected $ravenClient; - - /** - * @var FormatterInterface The formatter to use for the logs generated via handleBatch() - */ - protected $batchFormatter; - - /** - * @param Raven_Client $ravenClient - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) - { - @trigger_error('The Monolog\Handler\RavenHandler class is deprecated. You should rather upgrade to the sentry/sentry 2.x and use Sentry\Monolog\Handler, see https://github.com/getsentry/sentry-php/blob/master/src/Monolog/Handler.php', E_USER_DEPRECATED); - - parent::__construct($level, $bubble); - - $this->ravenClient = $ravenClient; - } - - /** - * {@inheritdoc} - */ - public function handleBatch(array $records) - { - $level = $this->level; - - // filter records based on their level - $records = array_filter($records, function ($record) use ($level) { - return $record['level'] >= $level; - }); - - if (!$records) { - return; - } - - // the record with the highest severity is the "main" one - $record = array_reduce($records, function ($highest, $record) { - if (null === $highest || $record['level'] > $highest['level']) { - return $record; - } - - return $highest; - }); - - // the other ones are added as a context item - $logs = array(); - foreach ($records as $r) { - $logs[] = $this->processRecord($r); - } - - if ($logs) { - $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); - } - - $this->handle($record); - } - - /** - * Sets the formatter for the logs generated by handleBatch(). - * - * @param FormatterInterface $formatter - */ - public function setBatchFormatter(FormatterInterface $formatter) - { - $this->batchFormatter = $formatter; - } - - /** - * Gets the formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - public function getBatchFormatter() - { - if (!$this->batchFormatter) { - $this->batchFormatter = $this->getDefaultBatchFormatter(); - } - - return $this->batchFormatter; - } - - /** - * {@inheritdoc} - */ - protected function write(array $record) - { - $previousUserContext = false; - $options = array(); - $options['level'] = $this->logLevels[$record['level']]; - $options['tags'] = array(); - if (!empty($record['extra']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['extra']['tags']); - unset($record['extra']['tags']); - } - if (!empty($record['context']['tags'])) { - $options['tags'] = array_merge($options['tags'], $record['context']['tags']); - unset($record['context']['tags']); - } - if (!empty($record['context']['fingerprint'])) { - $options['fingerprint'] = $record['context']['fingerprint']; - unset($record['context']['fingerprint']); - } - if (!empty($record['context']['logger'])) { - $options['logger'] = $record['context']['logger']; - unset($record['context']['logger']); - } else { - $options['logger'] = $record['channel']; - } - foreach ($this->getExtraParameters() as $key) { - foreach (array('extra', 'context') as $source) { - if (!empty($record[$source][$key])) { - $options[$key] = $record[$source][$key]; - unset($record[$source][$key]); - } - } - } - if (!empty($record['context'])) { - $options['extra']['context'] = $record['context']; - if (!empty($record['context']['user'])) { - $previousUserContext = $this->ravenClient->context->user; - $this->ravenClient->user_context($record['context']['user']); - unset($options['extra']['context']['user']); - } - } - if (!empty($record['extra'])) { - $options['extra']['extra'] = $record['extra']; - } - - if (!empty($this->release) && !isset($options['release'])) { - $options['release'] = $this->release; - } - - if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { - $options['message'] = $record['formatted']; - $this->ravenClient->captureException($record['context']['exception'], $options); - } else { - $this->ravenClient->captureMessage($record['formatted'], array(), $options); - } - - if ($previousUserContext !== false) { - $this->ravenClient->user_context($previousUserContext); - } - } - - /** - * {@inheritDoc} - */ - protected function getDefaultFormatter() - { - return new LineFormatter('[%channel%] %message%'); - } - - /** - * Gets the default formatter for the logs generated by handleBatch(). - * - * @return FormatterInterface - */ - protected function getDefaultBatchFormatter() - { - return new LineFormatter(); - } - - /** - * Gets extra parameters supported by Raven that can be found in "extra" and "context" - * - * @return array - */ - protected function getExtraParameters() - { - return array('contexts', 'checksum', 'release', 'event_id'); - } - - /** - * @param string $value - * @return self - */ - public function setRelease($value) - { - $this->release = $value; - - return $this; - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php index 3725db24..91d16eaf 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -1,4 +1,4 @@ -pushHandler($redis); * * @author Thomas Tourlourat + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class RedisHandler extends AbstractProcessingHandler { + /** @var \Predis\Client<\Predis\Client>|\Redis */ private $redisClient; + /** @var string */ private $redisKey; + /** @var int */ protected $capSize; /** - * @param \Predis\Client|\Redis $redis The redis instance + * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance * @param string $key The key name to push records to - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param int|false $capSize Number of entries to limit list size to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited */ - public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false) + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true, int $capSize = 0) { if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { throw new \InvalidArgumentException('Predis\Client or Redis instance required'); @@ -54,7 +58,7 @@ public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true /** * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if ($this->capSize) { $this->writeCapped($record); @@ -67,10 +71,9 @@ protected function write(array $record) * Write and cap the collection * Writes the record to the redis list and caps its * - * @param array $record associative record array - * @return void + * @phpstan-param FormattedRecord $record */ - protected function writeCapped(array $record) + protected function writeCapped(array $record): void { if ($this->redisClient instanceof \Redis) { $mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1; @@ -91,7 +94,7 @@ protected function writeCapped(array $record) /** * {@inheritDoc} */ - protected function getDefaultFormatter() + protected function getDefaultFormatter(): FormatterInterface { return new LineFormatter(); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 00000000..7789309c --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Logger::WARNING); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var \Predis\Client<\Predis\Client>|\Redis */ + private $redisClient; + /** @var string */ + private $channelKey; + + /** + * @param \Predis\Client<\Predis\Client>|\Redis $redis The redis instance + * @param string $key The channel key to publish records to + */ + public function __construct($redis, string $key, $level = Logger::DEBUG, bool $bubble = true) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + $this->redisClient->publish($this->channelKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php index 65073ffe..adcc9395 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -1,4 +1,4 @@ - 'debug', Logger::INFO => 'info', Logger::NOTICE => 'info', @@ -49,7 +48,7 @@ class RollbarHandler extends AbstractProcessingHandler Logger::CRITICAL => 'critical', Logger::ALERT => 'critical', Logger::EMERGENCY => 'critical', - ); + ]; /** * Records whether any log records have been added since the last flush of the rollbar notifier @@ -58,24 +57,23 @@ class RollbarHandler extends AbstractProcessingHandler */ private $hasRecords = false; + /** @var bool */ protected $initialized = false; /** - * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token */ - public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true) + public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, bool $bubble = true) { - $this->rollbarNotifier = $rollbarNotifier; + $this->rollbarLogger = $rollbarLogger; parent::__construct($level, $bubble); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if (!$this->initialized) { // __destructor() doesn't get called on Fatal errors @@ -84,54 +82,45 @@ protected function write(array $record) } $context = $record['context']; - $payload = array(); - if (isset($context['payload'])) { - $payload = $context['payload']; - unset($context['payload']); - } - $context = array_merge($context, $record['extra'], array( + $context = array_merge($context, $record['extra'], [ 'level' => $this->levelMap[$record['level']], 'monolog_level' => $record['level_name'], 'channel' => $record['channel'], 'datetime' => $record['datetime']->format('U'), - )); + ]); - if (isset($context['exception']) && $context['exception'] instanceof Exception) { - $payload['level'] = $context['level']; + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { $exception = $context['exception']; unset($context['exception']); - - $this->rollbarNotifier->report_exception($exception, $context, $payload); + $toLog = $exception; } else { - $this->rollbarNotifier->report_message( - $record['message'], - $context['level'], - $context, - $payload - ); + $toLog = $record['message']; } + // @phpstan-ignore-next-line + $this->rollbarLogger->log($context['level'], $toLog, $context); + $this->hasRecords = true; } - public function flush() + public function flush(): void { if ($this->hasRecords) { - $this->rollbarNotifier->flush(); + $this->rollbarLogger->flush(); $this->hasRecords = false; } } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { $this->flush(); } /** - * {@inheritdoc} + * {@inheritDoc} */ public function reset() { @@ -139,6 +128,4 @@ public function reset() parent::reset(); } - - } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php index b8253ba0..172685e0 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -1,4 +1,4 @@ -filename = Utils::canonicalizePath($filename); - $this->maxFiles = (int) $maxFiles; - $this->nextRotation = new \DateTime('tomorrow'); + $this->maxFiles = $maxFiles; + $this->nextRotation = new \DateTimeImmutable('tomorrow'); $this->filenameFormat = '{filename}-{date}'; - $this->dateFormat = 'Y-m-d'; + $this->dateFormat = static::FILE_PER_DAY; parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { parent::close(); @@ -68,7 +73,7 @@ public function close() } /** - * {@inheritdoc} + * {@inheritDoc} */ public function reset() { @@ -79,55 +84,62 @@ public function reset() } } - public function setFilenameFormat($filenameFormat, $dateFormat) + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self { - if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { - trigger_error( + if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( 'Invalid date format - format must be one of '. 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. - 'date formats using slashes, underscores and/or dots instead of dashes.', - E_USER_DEPRECATED + 'date formats using slashes, underscores and/or dots instead of dashes.' ); } if (substr_count($filenameFormat, '{date}') === 0) { - trigger_error( - 'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.', - E_USER_DEPRECATED + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' ); } $this->filenameFormat = $filenameFormat; $this->dateFormat = $dateFormat; $this->url = $this->getTimedFilename(); $this->close(); + + return $this; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { - // on the first record written, if the log is new, we should rotate (once per day) + // on the first record written, if the log is new, we rotate (once per day) after the log has been written so that the new file exists if (null === $this->mustRotate) { - $this->mustRotate = !file_exists($this->url); + $this->mustRotate = null === $this->url || !file_exists($this->url); } - if ($this->nextRotation < $record['datetime']) { + // if the next rotation is expired, then we rotate immediately + if ($this->nextRotation <= $record['datetime']) { $this->mustRotate = true; - $this->close(); + $this->close(); // triggers rotation } parent::write($record); + + if ($this->mustRotate) { + $this->close(); // triggers rotation + } } /** * Rotates the files. */ - protected function rotate() + protected function rotate(): void { // update filename $this->url = $this->getTimedFilename(); - $this->nextRotation = new \DateTime('tomorrow'); + $this->nextRotation = new \DateTimeImmutable('tomorrow'); + + $this->mustRotate = false; // skip GC of old logs if files are unlimited if (0 === $this->maxFiles) { @@ -135,6 +147,11 @@ protected function rotate() } $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + if ($this->maxFiles >= count($logFiles)) { // no files to remove return; @@ -149,40 +166,44 @@ protected function rotate() if (is_writable($file)) { // suppress errors here as unlink() might fail if two processes // are cleaning up/rotating at the same time - set_error_handler(function ($errno, $errstr, $errfile, $errline) {}); + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return true; + }); unlink($file); restore_error_handler(); } } - - $this->mustRotate = false; } - protected function getTimedFilename() + protected function getTimedFilename(): string { $fileInfo = pathinfo($this->filename); $timedFilename = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], date($this->dateFormat)), + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $timedFilename .= '.'.$fileInfo['extension']; } return $timedFilename; } - protected function getGlobPattern() + protected function getGlobPattern(): string { $fileInfo = pathinfo($this->filename); $glob = str_replace( - array('{filename}', '{date}'), - array($fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'), + ['{filename}', '{date}'], + [$fileInfo['filename'], str_replace( + ['Y', 'y', 'm', 'd'], + ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], + $this->dateFormat) + ], $fileInfo['dirname'] . '/' . $this->filenameFormat ); - if (!empty($fileInfo['extension'])) { + if (isset($fileInfo['extension'])) { $glob .= '.'.$fileInfo['extension']; } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php index b547ed7d..25cce07f 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -1,4 +1,4 @@ - * @author Kunal Mehta + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ -class SamplingHandler extends AbstractHandler +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface { + use ProcessableHandlerTrait; + /** - * @var callable|HandlerInterface $handler + * @var HandlerInterface|callable + * @phpstan-var HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface */ protected $handler; @@ -40,10 +46,12 @@ class SamplingHandler extends AbstractHandler protected $factor; /** + * @psalm-param HandlerInterface|callable(Record|array{level: Level}|null, HandlerInterface): HandlerInterface $handler + * * @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler). - * @param int $factor Sample factor + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) */ - public function __construct($handler, $factor) + public function __construct($handler, int $factor) { parent::__construct(); $this->handler = $handler; @@ -54,18 +62,17 @@ public function __construct($handler, $factor) } } - public function isHandling(array $record) + public function isHandling(array $record): bool { return $this->getHandler($record)->isHandling($record); } - public function handle(array $record) + public function handle(array $record): bool { if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } $this->getHandler($record)->handle($record); @@ -79,12 +86,14 @@ public function handle(array $record) * * If the handler was provided as a factory callable, this will trigger the handler's instantiation. * + * @phpstan-param Record|array{level: Level}|null $record + * * @return HandlerInterface */ - public function getHandler(array $record = null) + public function getHandler(?array $record = null) { if (!$this->handler instanceof HandlerInterface) { - $this->handler = call_user_func($this->handler, $record, $this); + $this->handler = ($this->handler)($record, $this); if (!$this->handler instanceof HandlerInterface) { throw new \RuntimeException("The factory callable should return a HandlerInterface"); } @@ -94,20 +103,30 @@ public function getHandler(array $record = null) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { - $this->getHandler()->setFormatter($formatter); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); - return $this; + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getFormatter() + public function getFormatter(): FormatterInterface { - return $this->getHandler()->getFormatter(); + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 00000000..1280ee70 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + * @var string + */ + protected $apiUser; + + /** + * The SendGrid API Key + * @var string + */ + protected $apiKey; + + /** + * The email addresses to which the message will be sent + * @var string + */ + protected $from; + + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected $to; + + /** + * The subject of the email + * @var string + */ + protected $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|string[] $to The recipients of the email + * @param string $subject The subject of the mail + */ + public function __construct(string $apiUser, string $apiKey, string $from, $to, string $subject, $level = Logger::ERROR, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php index 39455501..9ae10037 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -1,4 +1,4 @@ - * @see https://api.slack.com/incoming-webhooks * @see https://api.slack.com/docs/message-attachments + * + * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler + * @phpstan-import-type Record from \Monolog\Logger */ class SlackRecord { - const COLOR_DANGER = 'danger'; + public const COLOR_DANGER = 'danger'; - const COLOR_WARNING = 'warning'; + public const COLOR_WARNING = 'warning'; - const COLOR_GOOD = 'good'; + public const COLOR_GOOD = 'good'; - const COLOR_DEFAULT = '#e3e4e6'; + public const COLOR_DEFAULT = '#e3e4e6'; /** * Slack channel (encoded ID or name) @@ -48,7 +51,7 @@ class SlackRecord /** * User icon e.g. 'ghost', 'http://example.com/user.png' - * @var string + * @var string|null */ private $userIcon; @@ -72,12 +75,12 @@ class SlackRecord /** * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] - * @var array + * @var string[] */ private $excludeFields; /** - * @var FormatterInterface + * @var ?FormatterInterface */ private $formatter; @@ -86,26 +89,45 @@ class SlackRecord */ private $normalizerFormatter; - public function __construct($channel = null, $username = null, $useAttachment = true, $userIcon = null, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array(), FormatterInterface $formatter = null) - { - $this->channel = $channel; - $this->username = $username; - $this->userIcon = trim($userIcon, ':'); - $this->useAttachment = $useAttachment; - $this->useShortAttachment = $useShortAttachment; - $this->includeContextAndExtra = $includeContextAndExtra; - $this->excludeFields = $excludeFields; - $this->formatter = $formatter; + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + ?FormatterInterface $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); } } - public function getSlackData(array $record) + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-param FormattedRecord $record + * @phpstan-return mixed[] + */ + public function getSlackData(array $record): array { $dataArray = array(); - $record = $this->excludeFields($record); + $record = $this->removeExcludedFields($record); if ($this->username) { $dataArray['username'] = $this->username; @@ -116,6 +138,7 @@ public function getSlackData(array $record) } if ($this->formatter && !$this->useAttachment) { + /** @phpstan-ignore-next-line */ $message = $this->formatter->format($record); } else { $message = $record['message']; @@ -123,12 +146,14 @@ public function getSlackData(array $record) if ($this->useAttachment) { $attachment = array( - 'fallback' => $message, - 'text' => $message, - 'color' => $this->getAttachmentColor($record['level']), - 'fields' => array(), - 'mrkdwn_in' => array('fields'), - 'ts' => $record['datetime']->getTimestamp() + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + 'mrkdwn_in' => array('fields'), + 'ts' => $record['datetime']->getTimestamp(), + 'footer' => $this->username, + 'footer_icon' => $this->userIcon, ); if ($this->useShortAttachment) { @@ -138,7 +163,6 @@ public function getSlackData(array $record) $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); } - if ($this->includeContextAndExtra) { foreach (array('extra', 'context') as $key) { if (empty($record[$key])) { @@ -147,7 +171,7 @@ public function getSlackData(array $record) if ($this->useShortAttachment) { $attachment['fields'][] = $this->generateAttachmentField( - $key, + (string) $key, $record[$key] ); } else { @@ -177,93 +201,157 @@ public function getSlackData(array $record) } /** - * Returned a Slack message attachment color associated with + * Returns a Slack message attachment color associated with * provided level. - * - * @param int $level - * @return string */ - public function getAttachmentColor($level) + public function getAttachmentColor(int $level): string { switch (true) { case $level >= Logger::ERROR: - return self::COLOR_DANGER; + return static::COLOR_DANGER; case $level >= Logger::WARNING: - return self::COLOR_WARNING; + return static::COLOR_WARNING; case $level >= Logger::INFO: - return self::COLOR_GOOD; + return static::COLOR_GOOD; default: - return self::COLOR_DEFAULT; + return static::COLOR_DEFAULT; } } /** * Stringifies an array of key/value pairs to be used in attachment fields * - * @param array $fields - * - * @return string + * @param mixed[] $fields */ - public function stringify($fields) + public function stringify(array $fields): string { + /** @var Record $fields */ $normalized = $this->normalizerFormatter->format($fields); - $prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128; - $flags = 0; - if (PHP_VERSION_ID >= 50400) { - $flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; - } $hasSecondDimension = count(array_filter($normalized, 'is_array')); $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric')); return $hasSecondDimension || $hasNonNumericKeys - ? Utils::jsonEncode($normalized, $prettyPrintFlag | $flags) - : Utils::jsonEncode($normalized, $flags); + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); } /** - * Sets the formatter + * Channel used by the bot when posting + * + * @param ?string $channel * - * @param FormatterInterface $formatter + * @return static + */ + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * + * @return static + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields */ - public function setFormatter(FormatterInterface $formatter) + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + public function setFormatter(?FormatterInterface $formatter = null): self { $this->formatter = $formatter; + + return $this; } /** * Generates attachment field * - * @param string $title - * @param string|array $value + * @param string|mixed[] $value * - * @return array + * @return array{title: string, value: string, short: false} */ - private function generateAttachmentField($title, $value) + private function generateAttachmentField(string $title, $value): array { $value = is_array($value) - ? sprintf('```%s```', $this->stringify($value)) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) : $value; return array( 'title' => ucfirst($title), 'value' => $value, - 'short' => false + 'short' => false, ); } /** * Generates a collection of attachment fields from array * - * @param array $data + * @param mixed[] $data * - * @return array + * @return array */ - private function generateAttachmentFields(array $data) + private function generateAttachmentFields(array $data): array { + /** @var Record $data */ + $normalized = $this->normalizerFormatter->format($data); + $fields = array(); - foreach ($this->normalizerFormatter->format($data) as $key => $value) { - $fields[] = $this->generateAttachmentField($key, $value); + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); } return $fields; @@ -272,11 +360,11 @@ private function generateAttachmentFields(array $data) /** * Get a copy of record with fields excluded according to $this->excludeFields * - * @param array $record + * @phpstan-param FormattedRecord $record * - * @return array + * @return mixed[] */ - private function excludeFields(array $record) + private function removeExcludedFields(array $record): array { foreach ($this->excludeFields as $field) { $keys = explode('.', $field); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php index 88c4c4d0..a648513e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -1,4 +1,4 @@ - * @see https://api.slack.com/ + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SlackHandler extends SocketHandler { @@ -42,20 +44,42 @@ class SlackHandler extends SocketHandler * @param string|null $username Name of a bot * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) * @param string|null $iconEmoji The emoji name to use (or null) - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style * @param bool $includeContextAndExtra Whether the attachment should include context and extra data - * @param array $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] * @throws MissingExtensionException If no OpenSSL PHP extension configured */ - public function __construct($token, $channel, $username = null, $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false, array $excludeFields = array()) - { + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Logger::CRITICAL, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = array(), + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { if (!extension_loaded('openssl')) { throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); } - parent::__construct('ssl://slack.com:443', $level, $bubble); + parent::__construct( + 'ssl://slack.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); $this->slackRecord = new SlackRecord( $channel, @@ -64,30 +88,26 @@ public function __construct($token, $channel, $username = null, $useAttachment = $iconEmoji, $useShortAttachment, $includeContextAndExtra, - $excludeFields, - $this->formatter + $excludeFields ); $this->token = $token; } - public function getSlackRecord() + public function getSlackRecord(): SlackRecord { return $this->slackRecord; } - public function getToken() + public function getToken(): string { return $this->token; } /** - * {@inheritdoc} - * - * @param array $record - * @return string + * {@inheritDoc} */ - protected function generateDataStream($record) + protected function generateDataStream(array $record): string { $content = $this->buildContent($record); @@ -97,10 +117,9 @@ protected function generateDataStream($record) /** * Builds the body of API call * - * @param array $record - * @return string + * @phpstan-param FormattedRecord $record */ - private function buildContent($record) + private function buildContent(array $record): string { $dataArray = $this->prepareContentData($record); @@ -108,12 +127,10 @@ private function buildContent($record) } /** - * Prepares content data - * - * @param array $record - * @return array + * @phpstan-param FormattedRecord $record + * @return string[] */ - protected function prepareContentData($record) + protected function prepareContentData(array $record): array { $dataArray = $this->slackRecord->getSlackData($record); $dataArray['token'] = $this->token; @@ -127,11 +144,8 @@ protected function prepareContentData($record) /** * Builds the header of the API Call - * - * @param string $content - * @return string */ - private function buildHeader($content) + private function buildHeader(string $content): string { $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; $header .= "Host: slack.com\r\n"; @@ -143,11 +157,9 @@ private function buildHeader($content) } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { parent::write($record); $this->finalizeWrite(); @@ -159,7 +171,7 @@ protected function write(array $record) * If we do not read some but close the socket too early, slack sometimes * drops the request entirely. */ - protected function finalizeWrite() + protected function finalizeWrite(): void { $res = $this->getResource(); if (is_resource($res)) { @@ -168,54 +180,77 @@ protected function finalizeWrite() $this->closeSocket(); } + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + /** - * Returned a Slack message attachment color associated with - * provided level. - * - * @param int $level - * @return string - * @deprecated Use underlying SlackRecord instead + * Channel used by the bot when posting */ - protected function getAttachmentColor($level) + public function setChannel(string $channel): self { - trigger_error( - 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', - E_USER_DEPRECATED - ); + $this->slackRecord->setChannel($channel); - return $this->slackRecord->getAttachmentColor($level); + return $this; } /** - * Stringifies an array of key/value pairs to be used in attachment fields - * - * @param array $fields - * @return string - * @deprecated Use underlying SlackRecord instead + * Username used by the bot when posting */ - protected function stringify($fields) + public function setUsername(string $username): self { - trigger_error( - 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', - E_USER_DEPRECATED - ); + $this->slackRecord->setUsername($username); - return $this->slackRecord->stringify($fields); + return $this; } - public function setFormatter(FormatterInterface $formatter) + public function useAttachment(bool $useAttachment): self { - parent::setFormatter($formatter); - $this->slackRecord->setFormatter($formatter); + $this->slackRecord->useAttachment($useAttachment); return $this; } - public function getFormatter() + public function setIconEmoji(string $iconEmoji): self { - $formatter = parent::getFormatter(); - $this->slackRecord->setFormatter($formatter); + $this->slackRecord->setUserIcon($iconEmoji); - return $formatter; + return $this; + } + + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php index b87be99a..8ae3c788 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -1,4 +1,4 @@ -webhookUrl = $webhookUrl; @@ -61,27 +73,24 @@ public function __construct($webhookUrl, $channel = null, $username = null, $use $iconEmoji, $useShortAttachment, $includeContextAndExtra, - $excludeFields, - $this->formatter + $excludeFields ); } - public function getSlackRecord() + public function getSlackRecord(): SlackRecord { return $this->slackRecord; } - public function getWebhookUrl() + public function getWebhookUrl(): string { return $this->webhookUrl; } /** - * {@inheritdoc} - * - * @param array $record + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $postData = $this->slackRecord->getSlackData($record); $postString = Utils::jsonEncode($postData); @@ -92,7 +101,7 @@ protected function write(array $record) CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => array('Content-type: application/json'), - CURLOPT_POSTFIELDS => $postString + CURLOPT_POSTFIELDS => $postString, ); if (defined('CURLOPT_SAFE_UPLOAD')) { $options[CURLOPT_SAFE_UPLOAD] = true; @@ -103,7 +112,7 @@ protected function write(array $record) Curl\Util::execute($ch); } - public function setFormatter(FormatterInterface $formatter) + public function setFormatter(FormatterInterface $formatter): HandlerInterface { parent::setFormatter($formatter); $this->slackRecord->setFormatter($formatter); @@ -111,7 +120,7 @@ public function setFormatter(FormatterInterface $formatter) return $this; } - public function getFormatter() + public function getFormatter(): FormatterInterface { $formatter = parent::getFormatter(); $this->slackRecord->setFormatter($formatter); diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php deleted file mode 100644 index d3352ea0..00000000 --- a/vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Logger; - -/** - * Sends notifications through Slack's Slackbot - * - * @author Haralan Dobrev - * @see https://slack.com/apps/A0F81R8ET-slackbot - * @deprecated According to Slack the API used on this handler it is deprecated. - * Therefore this handler will be removed on 2.x - * Slack suggests to use webhooks instead. Please contact slack for more information. - */ -class SlackbotHandler extends AbstractProcessingHandler -{ - /** - * The slug of the Slack team - * @var string - */ - private $slackTeam; - - /** - * Slackbot token - * @var string - */ - private $token; - - /** - * Slack channel name - * @var string - */ - private $channel; - - /** - * @param string $slackTeam Slack team slug - * @param string $token Slackbot token - * @param string $channel Slack channel (encoded ID or name) - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - */ - public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) - { - @trigger_error('SlackbotHandler is deprecated and will be removed on 2.x', E_USER_DEPRECATED); - parent::__construct($level, $bubble); - - $this->slackTeam = $slackTeam; - $this->token = $token; - $this->channel = $channel; - } - - /** - * {@inheritdoc} - * - * @param array $record - */ - protected function write(array $record) - { - $slackbotUrl = sprintf( - 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', - $this->slackTeam, - $this->token, - $this->channel - ); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $slackbotUrl); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); - - Curl\Util::execute($ch); - } -} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php index db50d97f..21701afa 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -1,4 +1,4 @@ - * @see http://php.net/manual/en/function.fsockopen.php + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class SocketHandler extends AbstractProcessingHandler { + /** @var string */ private $connectionString; + /** @var float */ private $connectionTimeout; + /** @var resource|null */ private $resource; - private $timeout = 0; - private $writingTimeout = 10; + /** @var float */ + private $timeout; + /** @var float */ + private $writingTimeout; + /** @var ?int */ private $lastSentBytes = null; - private $chunkSize = null; - private $persistent = false; - private $errno; - private $errstr; - private $lastWritingAt; + /** @var ?int */ + private $chunkSize; + /** @var bool */ + private $persistent; + /** @var ?int */ + private $errno = null; + /** @var ?string */ + private $errstr = null; + /** @var ?float */ + private $lastWritingAt = null; /** - * @param string $connectionString Socket connection string - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $connectionString Socket connection string + * @param bool $persistent Flag to enable/disable persistent connections + * @param float $timeout Socket timeout to wait until the request is being aborted + * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written + * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been + * established + * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle + * + * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. */ - public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) - { + public function __construct( + string $connectionString, + $level = Logger::DEBUG, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { parent::__construct($level, $bubble); $this->connectionString = $connectionString; - $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + + if ($connectionTimeout !== null) { + $this->validateTimeout($connectionTimeout); + } + + $this->connectionTimeout = $connectionTimeout ?? (float) ini_get('default_socket_timeout'); + $this->persistent = $persistent; + $this->validateTimeout($timeout); + $this->timeout = $timeout; + $this->validateTimeout($writingTimeout); + $this->writingTimeout = $writingTimeout; + $this->chunkSize = $chunkSize; } /** * Connect (if necessary) and write to the socket * - * @param array $record + * {@inheritDoc} * * @throws \UnexpectedValueException * @throws \RuntimeException */ - protected function write(array $record) + protected function write(array $record): void { $this->connectIfNotConnected(); $data = $this->generateDataStream($record); @@ -63,7 +102,7 @@ protected function write(array $record) /** * We will not close a PersistentSocket instance so it can be reused in other requests. */ - public function close() + public function close(): void { if (!$this->isPersistent()) { $this->closeSocket(); @@ -73,7 +112,7 @@ public function close() /** * Close socket, if open */ - public function closeSocket() + public function closeSocket(): void { if (is_resource($this->resource)) { fclose($this->resource); @@ -82,39 +121,39 @@ public function closeSocket() } /** - * Set socket connection to nbe persistent. It only has effect before the connection is initiated. - * - * @param bool $persistent + * Set socket connection to be persistent. It only has effect before the connection is initiated. */ - public function setPersistent($persistent) + public function setPersistent(bool $persistent): self { - $this->persistent = (bool) $persistent; + $this->persistent = $persistent; + + return $this; } /** * Set connection timeout. Only has effect before we connect. * - * @param float $seconds - * * @see http://php.net/manual/en/function.fsockopen.php */ - public function setConnectionTimeout($seconds) + public function setConnectionTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->connectionTimeout = (float) $seconds; + $this->connectionTimeout = $seconds; + + return $this; } /** * Set write timeout. Only has effect before we connect. * - * @param float $seconds - * * @see http://php.net/manual/en/function.stream-set-timeout.php */ - public function setTimeout($seconds) + public function setTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->timeout = (float) $seconds; + $this->timeout = $seconds; + + return $this; } /** @@ -122,58 +161,52 @@ public function setTimeout($seconds) * * @param float $seconds 0 for no timeout */ - public function setWritingTimeout($seconds) + public function setWritingTimeout(float $seconds): self { $this->validateTimeout($seconds); - $this->writingTimeout = (float) $seconds; + $this->writingTimeout = $seconds; + + return $this; } /** * Set chunk size. Only has effect during connection in the writing cycle. - * - * @param float $bytes */ - public function setChunkSize($bytes) + public function setChunkSize(int $bytes): self { $this->chunkSize = $bytes; + + return $this; } /** * Get current connection string - * - * @return string */ - public function getConnectionString() + public function getConnectionString(): string { return $this->connectionString; } /** * Get persistent setting - * - * @return bool */ - public function isPersistent() + public function isPersistent(): bool { return $this->persistent; } /** * Get current connection timeout setting - * - * @return float */ - public function getConnectionTimeout() + public function getConnectionTimeout(): float { return $this->connectionTimeout; } /** * Get current in-transfer timeout - * - * @return float */ - public function getTimeout() + public function getTimeout(): float { return $this->timeout; } @@ -183,17 +216,15 @@ public function getTimeout() * * @return float */ - public function getWritingTimeout() + public function getWritingTimeout(): float { return $this->writingTimeout; } /** * Get current chunk size - * - * @return float */ - public function getChunkSize() + public function getChunkSize(): ?int { return $this->chunkSize; } @@ -202,10 +233,8 @@ public function getChunkSize() * Check to see if the socket is currently available. * * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. - * - * @return bool */ - public function isConnected() + public function isConnected(): bool { return is_resource($this->resource) && !feof($this->resource); // on TCP - other party can close connection. @@ -213,6 +242,8 @@ public function isConnected() /** * Wrapper to allow mocking + * + * @return resource|false */ protected function pfsockopen() { @@ -221,6 +252,8 @@ protected function pfsockopen() /** * Wrapper to allow mocking + * + * @return resource|false */ protected function fsockopen() { @@ -231,50 +264,77 @@ protected function fsockopen() * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-timeout.php + * + * @return bool */ protected function streamSetTimeout() { $seconds = floor($this->timeout); $microseconds = round(($this->timeout - $seconds) * 1e6); - return stream_set_timeout($this->resource, $seconds, $microseconds); + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); } /** * Wrapper to allow mocking * * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|bool */ protected function streamSetChunkSize() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + return stream_set_chunk_size($this->resource, $this->chunkSize); } /** * Wrapper to allow mocking + * + * @return int|bool */ - protected function fwrite($data) + protected function fwrite(string $data) { + if (!is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + return @fwrite($this->resource, $data); } /** * Wrapper to allow mocking + * + * @return mixed[]|bool */ protected function streamGetMetadata() { + if (!is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + return stream_get_meta_data($this->resource); } - private function validateTimeout($value) + private function validateTimeout(float $value): void { - $ok = filter_var($value, FILTER_VALIDATE_FLOAT); - if ($ok === false || $value < 0) { + if ($value < 0) { throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); } } - private function connectIfNotConnected() + private function connectIfNotConnected(): void { if ($this->isConnected()) { return; @@ -282,7 +342,10 @@ private function connectIfNotConnected() $this->connect(); } - protected function generateDataStream($record) + /** + * @phpstan-param FormattedRecord $record + */ + protected function generateDataStream(array $record): string { return (string) $record['formatted']; } @@ -295,41 +358,41 @@ protected function getResource() return $this->resource; } - private function connect() + private function connect(): void { $this->createSocketResource(); $this->setSocketTimeout(); $this->setStreamChunkSize(); } - private function createSocketResource() + private function createSocketResource(): void { if ($this->isPersistent()) { $resource = $this->pfsockopen(); } else { $resource = $this->fsockopen(); } - if (!$resource) { + if (is_bool($resource)) { throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); } $this->resource = $resource; } - private function setSocketTimeout() + private function setSocketTimeout(): void { if (!$this->streamSetTimeout()) { throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); } } - private function setStreamChunkSize() + private function setStreamChunkSize(): void { if ($this->chunkSize && !$this->streamSetChunkSize()) { throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); } } - private function writeToSocket($data) + private function writeToSocket(string $data): void { $length = strlen($data); $sent = 0; @@ -345,7 +408,7 @@ private function writeToSocket($data) } $sent += $chunk; $socketInfo = $this->streamGetMetadata(); - if ($socketInfo['timed_out']) { + if (is_array($socketInfo) && $socketInfo['timed_out']) { throw new \RuntimeException("Write timed-out"); } @@ -358,15 +421,15 @@ private function writeToSocket($data) } } - private function writingIsTimedOut($sent) + private function writingIsTimedOut(int $sent): bool { - $writingTimeout = (int) floor($this->writingTimeout); - if (0 === $writingTimeout) { + // convert to ms + if (0.0 == $this->writingTimeout) { return false; } if ($sent !== $this->lastSentBytes) { - $this->lastWritingAt = time(); + $this->lastWritingAt = microtime(true); $this->lastSentBytes = $sent; return false; @@ -374,7 +437,7 @@ private function writingIsTimedOut($sent) usleep(100); } - if ((time() - $this->lastWritingAt) >= $writingTimeout) { + if ((microtime(true) - $this->lastWritingAt) >= $this->writingTimeout) { $this->closeSocket(); return true; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 00000000..dcf282b4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + /** @var SqsClient */ + private $client; + /** @var string */ + private $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, $level = Logger::DEBUG, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void + { + if (!isset($record['formatted']) || 'string' !== gettype($record['formatted'])) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); + } + + $messageBody = $record['formatted']; + if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php index 74a613cb..218d4384 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class StreamHandler extends AbstractProcessingHandler { - /** @private 512KB */ - const CHUNK_SIZE = 524288; - + /** @const int */ + protected const MAX_CHUNK_SIZE = 2147483647; + /** @const int 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + /** @var int */ + protected $streamChunkSize; /** @var resource|null */ protected $stream; - protected $url; - private $errorMessage; + /** @var ?string */ + protected $url = null; + /** @var ?string */ + private $errorMessage = null; + /** @var ?int */ protected $filePermission; + /** @var bool */ protected $useLocking; - private $dirCreated; + /** @var string */ + protected $fileOpenMode; + /** @var true|null */ + private $dirCreated = null; + /** @var bool */ + private $retrying = false; /** - * @param resource|string $stream - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) * @param bool $useLocking Try to lock log file before doing any writes + * @param string $fileOpenMode The fopen() mode used when opening a file, if $stream is a file path * - * @throws \Exception If a missing directory is not buildable * @throws \InvalidArgumentException If stream is not a resource or string */ - public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + public function __construct($stream, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, $fileOpenMode = 'a') { parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + if (is_resource($stream)) { $this->stream = $stream; - $this->streamSetChunkSize(); + + stream_set_chunk_size($this->stream, $this->streamChunkSize); } elseif (is_string($stream)) { $this->url = Utils::canonicalizePath($stream); } else { throw new \InvalidArgumentException('A stream must either be a resource or a string.'); } + $this->fileOpenMode = $fileOpenMode; $this->filePermission = $filePermission; $this->useLocking = $useLocking; } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { if ($this->url && is_resource($this->stream)) { fclose($this->stream); @@ -87,78 +115,110 @@ public function getStream() * * @return string|null */ - public function getUrl() + public function getUrl(): ?string { return $this->url; } /** - * {@inheritdoc} + * @return int */ - protected function write(array $record) + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record): void { if (!is_resource($this->stream)) { - if (null === $this->url || '' === $this->url) { - throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + $url = $this->url; + if (null === $url || '' === $url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); } - $this->createDir(); + $this->createDir($url); $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); - $this->stream = fopen($this->url, 'a'); - if ($this->filePermission !== null) { - @chmod($this->url, $this->filePermission); + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); + try { + $stream = fopen($url, $this->fileOpenMode); + if ($this->filePermission !== null) { + @chmod($url, $this->filePermission); + } + } finally { + restore_error_handler(); } - restore_error_handler(); - if (!is_resource($this->stream)) { + if (!is_resource($stream)) { $this->stream = null; - throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $this->url)); + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record)); } - $this->streamSetChunkSize(); + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + } + + $stream = $this->stream; + if (!is_resource($stream)) { + throw new \LogicException('No stream was opened yet' . Utils::getRecordMessageForException($record)); } if ($this->useLocking) { // ignoring errors here, there's not much we can do about them - flock($this->stream, LOCK_EX); + flock($stream, LOCK_EX); + } + + $this->errorMessage = null; + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); + try { + $this->streamWrite($stream, $record); + } finally { + restore_error_handler(); } + if ($this->errorMessage !== null) { + $error = $this->errorMessage; + // close the resource if possible to reopen it, and retry the failed write + if (!$this->retrying && $this->url !== null && $this->url !== 'php://memory') { + $this->retrying = true; + $this->close(); + $this->write($record); - $this->streamWrite($this->stream, $record); + return; + } + + throw new \UnexpectedValueException('Writing to the log file failed: '.$error . Utils::getRecordMessageForException($record)); + } + $this->retrying = false; if ($this->useLocking) { - flock($this->stream, LOCK_UN); + flock($stream, LOCK_UN); } } /** * Write to stream * @param resource $stream - * @param array $record + * @param array $record + * + * @phpstan-param FormattedRecord $record */ - protected function streamWrite($stream, array $record) + protected function streamWrite($stream, array $record): void { fwrite($stream, (string) $record['formatted']); } - protected function streamSetChunkSize() + private function customErrorHandler(int $code, string $msg): bool { - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - return stream_set_chunk_size($this->stream, self::CHUNK_SIZE); - } - - return false; - } + $this->errorMessage = preg_replace('{^(fopen|mkdir|fwrite)\(.*?\): }', '', $msg); - private function customErrorHandler($code, $msg) - { - $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + return true; } - /** - * @param string $stream - * - * @return null|string - */ - private function getDirFromStream($stream) + private function getDirFromStream(string $stream): ?string { $pos = strpos($stream, '://'); if ($pos === false) { @@ -172,21 +232,23 @@ private function getDirFromStream($stream) return null; } - private function createDir() + private function createDir(string $url): void { // Do not try to create dir if it has already been tried. if ($this->dirCreated) { return; } - $dir = $this->getDirFromStream($this->url); + $dir = $this->getDirFromStream($url); if (null !== $dir && !is_dir($dir)) { $this->errorMessage = null; - set_error_handler(array($this, 'customErrorHandler')); + set_error_handler(function (...$args) { + return $this->customErrorHandler(...$args); + }); $status = mkdir($dir, 0777, true); restore_error_handler(); - if (false === $status && !is_dir($dir)) { - throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir)); + if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); } } $this->dirCreated = true; diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php index ac7b16ff..fae92514 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -1,4 +1,4 @@ -mailer = $mailer; $this->messageTemplate = $message; } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function send($content, array $records) + protected function send(string $content, array $records): void { $this->mailer->send($this->buildMessage($content, $records)); } @@ -51,10 +60,9 @@ protected function send($content, array $records) /** * Gets the formatter for the Swift_Message subject. * - * @param string $format The format of the subject - * @return FormatterInterface + * @param string|null $format The format of the subject */ - protected function getSubjectFormatter($format) + protected function getSubjectFormatter(?string $format): FormatterInterface { return new LineFormatter($format); } @@ -62,22 +70,25 @@ protected function getSubjectFormatter($format) /** * Creates instance of Swift_Message to be sent * - * @param string $content formatted email body to be sent - * @param array $records Log records that formed the content - * @return \Swift_Message + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * @return Swift_Message + * + * @phpstan-param Record[] $records */ - protected function buildMessage($content, array $records) + protected function buildMessage(string $content, array $records): Swift_Message { $message = null; - if ($this->messageTemplate instanceof \Swift_Message) { + if ($this->messageTemplate instanceof Swift_Message) { $message = clone $this->messageTemplate; $message->generateId(); } elseif (is_callable($this->messageTemplate)) { - $message = call_user_func($this->messageTemplate, $content, $records); + $message = ($this->messageTemplate)($content, $records); } - if (!$message instanceof \Swift_Message) { - throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it'); + if (!$message instanceof Swift_Message) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); } if ($records) { @@ -85,27 +96,20 @@ protected function buildMessage($content, array $records) $message->setSubject($subjectFormatter->format($this->getHighestRecord($records))); } - $message->setBody($content); + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ if (version_compare(Swift::VERSION, '6.0.0', '>=')) { $message->setDate(new \DateTimeImmutable()); } else { + /** @phpstan-ignore-next-line */ $message->setDate(time()); } return $message; } - - /** - * BC getter, to be removed in 2.0 - */ - public function __get($name) - { - if ($name === 'message') { - trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED); - - return $this->buildMessage(null, array()); - } - - throw new \InvalidArgumentException('Invalid property '.$name); - } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php new file mode 100644 index 00000000..130e6f1f --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * SymfonyMailerHandler uses Symfony's Mailer component to send the emails + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class SymfonyMailerHandler extends MailHandler +{ + /** @var MailerInterface|TransportInterface */ + protected $mailer; + /** @var Email|callable(string, Record[]): Email */ + private $emailTemplate; + + /** + * @psalm-param Email|callable(string, Record[]): Email $email + * + * @param MailerInterface|TransportInterface $mailer The mailer to use + * @param callable|Email $email An email template, the subject/body will be replaced + */ + public function __construct($mailer, $email, $level = Logger::ERROR, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->emailTemplate = $email; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Email to be sent + * + * @param string $content formatted email body to be sent + * @param array $records Log records that formed the content + * + * @phpstan-param Record[] $records + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->emailTemplate instanceof Email) { + $message = clone $this->emailTemplate; + } elseif (is_callable($this->emailTemplate)) { + $message = ($this->emailTemplate)($content, $records); + } + + if (!$message instanceof Email) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record ? Utils::getRecordMessageForException($record) : '')); + } + + if ($records) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->isHtmlBody($content)) { + if (null !== ($charset = $message->getHtmlCharset())) { + $message->html($content, $charset); + } else { + $message->html($content); + } + } else { + if (null !== ($charset = $message->getTextCharset())) { + $message->text($content, $charset); + } else { + $message->text($content); + } + } + + return $message->date(new \DateTimeImmutable()); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php index f770c802..1d543b7e 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -1,4 +1,4 @@ -facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID */ - public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + public function __construct(string $ident, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, int $logopts = LOG_PID) { parent::__construct($facility, $level, $bubble); @@ -47,20 +48,20 @@ public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function close() + public function close(): void { closelog(); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { if (!openlog($this->ident, $this->logopts, $this->facility)) { - throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"' . Utils::getRecordMessageForException($record)); } syslog($this->logLevels[$record['level']], (string) $record['formatted']); } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php index 3bff085b..dbd8ef69 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -1,4 +1,4 @@ -ip = $ip; $this->port = $port; - $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); } + /** + * @param string $line + * @param string $header + * @return void + */ public function write($line, $header = "") { $this->send($this->assembleMessage($line, $header)); } - public function close() + public function close(): void { - if (is_resource($this->socket)) { + if (is_resource($this->socket) || $this->socket instanceof Socket) { socket_close($this->socket); $this->socket = null; } } - protected function send($chunk) + /** + * @return resource|Socket + */ + protected function getSocket() { - if (!is_resource($this->socket)) { - throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore'); + if (null !== $this->socket) { + return $this->socket; + } + + $domain = AF_INET; + $protocol = SOL_UDP; + // Check if we are using unix sockets. + if ($this->port === 0) { + $domain = AF_UNIX; + $protocol = IPPROTO_IP; + } + + $this->socket = socket_create($domain, SOCK_DGRAM, $protocol) ?: null; + if (null === $this->socket) { + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create'); } - socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + + return $this->socket; + } + + protected function send(string $chunk): void + { + socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); } - protected function assembleMessage($line, $header) + protected function assembleMessage(string $line, string $header): string { - $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header); + $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); - return $header . substr($line, 0, $chunkSize); + return $header . Utils::substr($line, 0, $chunkSize); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php index 4dfd5f5e..deaa19f8 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -1,4 +1,4 @@ - */ private $dateFormats = array( self::RFC3164 => 'M d H:i:s', self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, ); + /** @var UdpSocket */ protected $socket; + /** @var string */ protected $ident; + /** @var self::RFC* */ protected $rfc; /** - * @param string $host - * @param int $port - * @param mixed $facility - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param string $ident Program name or tag for each log message. - * @param int $rfc RFC to format the message for. + * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) + * @param int $port Port number, or 0 if $host is a unix socket + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + * @throws MissingExtensionException + * + * @phpstan-param self::RFC* $rfc */ - public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php', $rfc = self::RFC5424) + public function __construct(string $host, int $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + parent::__construct($facility, $level, $bubble); $this->ident = $ident; $this->rfc = $rfc; - $this->socket = new UdpSocket($host, $port ?: 514); + $this->socket = new UdpSocket($host, $port); } - protected function write(array $record) + protected function write(array $record): void { $lines = $this->splitMessageIntoLines($record['formatted']); - $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]); + $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']], $record['datetime']); foreach ($lines as $line) { $this->socket->write($line, $header); } } - public function close() + public function close(): void { $this->socket->close(); } - private function splitMessageIntoLines($message) + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array { if (is_array($message)) { $message = implode("\n", $message); } - return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY); + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $lines; } /** * Make common syslog header (see rfc5424 or rfc3164) */ - protected function makeCommonSyslogHeader($severity) + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string { $priority = $severity + $this->facility; @@ -93,32 +117,34 @@ protected function makeCommonSyslogHeader($severity) $hostname = '-'; } - $date = $this->getDateTime(); - if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + return "<$priority>" . $date . " " . $hostname . " " . $this->ident . "[" . $pid . "]: "; - } else { - return "<$priority>1 " . - $date . " " . - $hostname . " " . - $this->ident . " " . - $pid . " - - "; } - } - protected function getDateTime() - { - return date($this->dateFormats[$this->rfc]); + $date = $datetime->format($this->dateFormats[$this->rfc]); + + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; } /** * Inject your own socket, mainly used for testing */ - public function setSocket($socket) + public function setSocket(UdpSocket $socket): self { $this->socket = $socket; + + return $this; } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 00000000..0b1ac71b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,278 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Logger; +use Monolog\Utils; + +/** + * Handler send logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create telegram bot with https://telegram.me/BotFather + * 2) Create a telegram channel where logs will be recorded. + * 3) Add created bot from step 1 to the created channel from step 2. + * + * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + * + * @phpstan-import-type Record from \Monolog\Logger + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * The maximum number of characters allowed in a message according to the Telegram api documentation + */ + private const MAX_MESSAGE_LENGTH = 4096; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + * @var string + */ + private $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + * @var string + */ + private $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + * @var ?string + */ + private $parseMode; + + /** + * Disables link previews for links in the message. + * @var ?bool + */ + private $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + * @var ?bool + */ + private $disableNotification; + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @var bool + */ + private $splitLongMessages; + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @var bool + */ + private $delayBetweenMessages; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages + * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API + * @throws MissingExtensionException + */ + public function __construct( + string $apiKey, + string $channel, + $level = Logger::DEBUG, + bool $bubble = true, + ?string $parseMode = null, + ?bool $disableWebPagePreview = null, + ?bool $disableNotification = null, + bool $splitLongMessages = false, + bool $delayBetweenMessages = false + ) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + $this->splitLongMessages($splitLongMessages); + $this->delayBetweenMessages($delayBetweenMessages); + } + + public function setParseMode(?string $parseMode = null): self + { + if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES)) { + throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + public function disableWebPagePreview(?bool $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + public function disableNotification(?bool $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @param bool $splitLongMessages + * @return $this + */ + public function splitLongMessages(bool $splitLongMessages = false): self + { + $this->splitLongMessages = $splitLongMessages; + + return $this; + } + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @param bool $delayBetweenMessages + * @return $this + */ + public function delayBetweenMessages(bool $delayBetweenMessages = false): self + { + $this->delayBetweenMessages = $delayBetweenMessages; + + return $this; + } + + /** + * {@inheritDoc} + */ + public function handleBatch(array $records): void + { + /** @var Record[] $messages */ + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if ($this->processors) { + /** @var Record $record */ + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (!empty($messages)) { + $this->send((string)$this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(array $record): void + { + $this->send($record['formatted']); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + * @param string $message + */ + protected function send(string $message): void + { + $messages = $this->handleMessageLength($message); + + foreach ($messages as $key => $msg) { + if ($this->delayBetweenMessages && $key > 0) { + sleep(1); + } + + $this->sendCurl($msg); + } + } + + protected function sendCurl(string $message): void + { + if ('' === trim($message)) { + return; + } + + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ])); + + $result = Curl\Util::execute($ch); + if (!is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } + $result = json_decode($result, true); + + if ($result['ok'] === false) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } + + /** + * Handle a message that is too long: truncates or splits into several + * @param string $message + * @return string[] + */ + private function handleMessageLength(string $message): array + { + $truncatedMarker = ' (...truncated)'; + if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) { + return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker]; + } + + return str_split($message, self::MAX_MESSAGE_LENGTH); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php index 478db0ac..0986da27 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -1,4 +1,4 @@ - */ + protected $recordsByLevel = []; + /** @var bool */ private $skipReset = false; + /** + * @return array + * + * @phpstan-return Record[] + */ public function getRecords() { return $this->records; } + /** + * @return void + */ public function clear() { - $this->records = array(); - $this->recordsByLevel = array(); + $this->records = []; + $this->recordsByLevel = []; } + /** + * @return void + */ public function reset() { if (!$this->skipReset) { @@ -87,21 +108,32 @@ public function reset() } } - public function setSkipReset($skipReset) + /** + * @return void + */ + public function setSkipReset(bool $skipReset) { $this->skipReset = $skipReset; } - public function hasRecords($level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecords($level): bool { - return isset($this->recordsByLevel[$level]); + return isset($this->recordsByLevel[Logger::toMonologLevel($level)]); } /** * @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records - * @param int $level Logger::LEVEL constant value + * @param string|int $level Logging level value or name + * + * @phpstan-param array{message: string, context?: mixed[]}|string $record + * @phpstan-param Level|LevelName|LogLevel::* $level */ - public function hasRecord($record, $level) + public function hasRecord($record, $level): bool { if (is_string($record)) { $record = array('message' => $record); @@ -114,36 +146,52 @@ public function hasRecord($record, $level) if (isset($record['context']) && $rec['context'] !== $record['context']) { return false; } + return true; }, $level); } - public function hasRecordThatContains($message, $level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatContains(string $message, $level): bool { return $this->hasRecordThatPasses(function ($rec) use ($message) { return strpos($rec['message'], $message) !== false; }, $level); } - public function hasRecordThatMatches($regex, $level) + /** + * @param string|int $level Logging level value or name + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatMatches(string $regex, $level): bool { - return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return $this->hasRecordThatPasses(function (array $rec) use ($regex): bool { return preg_match($regex, $rec['message']) > 0; }, $level); } - public function hasRecordThatPasses($predicate, $level) + /** + * @param string|int $level Logging level value or name + * @return bool + * + * @psalm-param callable(Record, int): mixed $predicate + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function hasRecordThatPasses(callable $predicate, $level) { - if (!is_callable($predicate)) { - throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds"); - } + $level = Logger::toMonologLevel($level); if (!isset($this->recordsByLevel[$level])) { return false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { - if (call_user_func($predicate, $rec, $i)) { + if ($predicate($rec, $i)) { return true; } } @@ -152,23 +200,29 @@ public function hasRecordThatPasses($predicate, $level) } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } + /** + * @param string $method + * @param mixed[] $args + * @return bool + */ public function __call($method, $args) { if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = constant('Monolog\Logger::' . strtoupper($matches[2])); - if (method_exists($this, $genericMethod)) { + $callback = [$this, $genericMethod]; + if (is_callable($callback)) { $args[] = $level; - return call_user_func_array(array($this, $genericMethod), $args); + return call_user_func_array($callback, $args); } } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 00000000..c8183528 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + * @return bool + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php index 7d7622a3..b6d3d3b1 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ class WhatFailureGroupHandler extends GroupHandler { /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handle(array $record) + public function handle(array $record): bool { if ($this->processors) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } + /** @var Record $record */ + $record = $this->processRecord($record); } foreach ($this->handlers as $handler) { try { $handler->handle($record); - } catch (\Exception $e) { - // What failure? } catch (\Throwable $e) { // What failure? } @@ -44,26 +43,36 @@ public function handle(array $record) } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function handleBatch(array $records) + public function handleBatch(array $records): void { if ($this->processors) { $processed = array(); foreach ($records as $record) { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - $processed[] = $record; + $processed[] = $this->processRecord($record); } + /** @var Record[] $records */ $records = $processed; } foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); - } catch (\Exception $e) { + } catch (\Throwable $e) { // What failure? + } + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + try { + $handler->close(); } catch (\Throwable $e) { // What failure? } diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php index a20aeae0..ddd46d8c 100644 --- a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -1,4 +1,5 @@ - * @author Jason Davis + * + * @phpstan-import-type FormattedRecord from AbstractProcessingHandler */ class ZendMonitorHandler extends AbstractProcessingHandler { /** * Monolog level / ZendMonitor Custom Event priority map * - * @var array + * @var array */ - protected $levelMap = array(); + protected $levelMap = []; /** - * Construct - * - * @param int $level - * @param bool $bubble * @throws MissingExtensionException */ - public function __construct($level = Logger::DEBUG, $bubble = true) + public function __construct($level = Logger::DEBUG, bool $bubble = true) { if (!function_exists('zend_monitor_custom_event')) { throw new MissingExtensionException( @@ -43,7 +43,7 @@ public function __construct($level = Logger::DEBUG, $bubble = true) ); } //zend monitor constants are not defined if zend monitor is not enabled. - $this->levelMap = array( + $this->levelMap = [ Logger::DEBUG => \ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::INFO => \ZEND_MONITOR_EVENT_SEVERITY_INFO, Logger::NOTICE => \ZEND_MONITOR_EVENT_SEVERITY_INFO, @@ -52,14 +52,14 @@ public function __construct($level = Logger::DEBUG, $bubble = true) Logger::CRITICAL => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::ALERT => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, Logger::EMERGENCY => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, - ); + ]; parent::__construct($level, $bubble); } /** - * {@inheritdoc} + * {@inheritDoc} */ - protected function write(array $record) + protected function write(array $record): void { $this->writeZendMonitorCustomEvent( Logger::getLevelName($record['level']), @@ -71,30 +71,30 @@ protected function write(array $record) /** * Write to Zend Monitor Events - * @param string $type Text displayed in "Class Name (custom)" field - * @param string $message Text displayed in "Error String" - * @param mixed $formatted Displayed in Custom Variables tab - * @param int $severity Set the event severity level (-1,0,1) + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + * + * @phpstan-param FormattedRecord $formatted */ - protected function writeZendMonitorCustomEvent($type, $message, $formatted, $severity) + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void { zend_monitor_custom_event($type, $message, $formatted, $severity); } /** - * {@inheritdoc} + * {@inheritDoc} */ - public function getDefaultFormatter() + public function getDefaultFormatter(): FormatterInterface { return new NormalizerFormatter(); } /** - * Get the level map - * - * @return array + * @return array */ - public function getLevelMap() + public function getLevelMap(): array { return $this->levelMap; } diff --git a/vendor/monolog/monolog/src/Monolog/LogRecord.php b/vendor/monolog/monolog/src/Monolog/LogRecord.php new file mode 100644 index 00000000..702807d7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/LogRecord.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use ArrayAccess; + +/** + * Monolog log record interface for forward compatibility with Monolog 3.0 + * + * This is just present in Monolog 2.4+ to allow interoperable code to be written against + * both versions by type-hinting arguments as `array|\Monolog\LogRecord $record` + * + * Do not rely on this interface for other purposes, and do not implement it. + * + * @author Jordi Boggiano + * @template-extends \ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra'|'formatted', mixed> + * @phpstan-import-type Record from Logger + */ +interface LogRecord extends \ArrayAccess +{ + /** + * @phpstan-return Record + */ + public function toArray(): array; +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php index 7d26b291..bf65d3c5 100644 --- a/vendor/monolog/monolog/src/Monolog/Logger.php +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -1,4 +1,4 @@ - + * + * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY + * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY' + * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} */ class Logger implements LoggerInterface, ResettableInterface { /** * Detailed debug information */ - const DEBUG = 100; + public const DEBUG = 100; /** * Interesting events * * Examples: User logs in, SQL logs. */ - const INFO = 200; + public const INFO = 200; /** * Uncommon events */ - const NOTICE = 250; + public const NOTICE = 250; /** * Exceptional occurrences that are not errors @@ -50,19 +56,19 @@ class Logger implements LoggerInterface, ResettableInterface * Examples: Use of deprecated APIs, poor use of an API, * undesirable things that are not necessarily wrong. */ - const WARNING = 300; + public const WARNING = 300; /** * Runtime errors */ - const ERROR = 400; + public const ERROR = 400; /** * Critical conditions * * Example: Application component unavailable, unexpected exception. */ - const CRITICAL = 500; + public const CRITICAL = 500; /** * Action must be taken immediately @@ -70,12 +76,12 @@ class Logger implements LoggerInterface, ResettableInterface * Example: Entire website down, database unavailable, etc. * This should trigger the SMS alerts and wake you up. */ - const ALERT = 550; + public const ALERT = 550; /** * Urgent alert. */ - const EMERGENCY = 600; + public const EMERGENCY = 600; /** * Monolog API version @@ -85,14 +91,16 @@ class Logger implements LoggerInterface, ResettableInterface * * @var int */ - const API = 1; + public const API = 2; /** - * Logging levels from syslog protocol defined in RFC 5424 + * This is a static variable and not a constant to serve as an extension point for custom levels + * + * @var array $levels Logging levels with the levels as key * - * @var array $levels Logging levels + * @phpstan-var array $levels Logging levels with the levels as key */ - protected static $levels = array( + protected static $levels = [ self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::NOTICE => 'NOTICE', @@ -101,12 +109,23 @@ class Logger implements LoggerInterface, ResettableInterface self::CRITICAL => 'CRITICAL', self::ALERT => 'ALERT', self::EMERGENCY => 'EMERGENCY', - ); + ]; /** - * @var \DateTimeZone + * Mapping between levels numbers defined in RFC 5424 and Monolog ones + * + * @phpstan-var array $rfc_5424_levels */ - protected static $timezone; + private const RFC_5424_LEVELS = [ + 7 => self::DEBUG, + 6 => self::INFO, + 5 => self::NOTICE, + 4 => self::WARNING, + 3 => self::ERROR, + 2 => self::CRITICAL, + 1 => self::ALERT, + 0 => self::EMERGENCY, + ]; /** * @var string @@ -135,36 +154,64 @@ class Logger implements LoggerInterface, ResettableInterface protected $microsecondTimestamps = true; /** - * @var callable + * @var DateTimeZone + */ + protected $timezone; + + /** + * @var callable|null */ protected $exceptionHandler; /** - * @param string $name The logging channel + * @var int Keeps track of depth to prevent infinite logging loops + */ + private $logDepth = 0; + + /** + * @var \WeakMap<\Fiber, int> Keeps track of depth inside fibers to prevent infinite logging loops + */ + private $fiberLogDepth; + + /** + * @var bool Whether to detect infinite logging loops + * + * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this + */ + private $detectCycles = true; + + /** + * @psalm-param array $processors + * + * @param string $name The logging channel, a simple descriptive name that is attached to all log records * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used */ - public function __construct($name, array $handlers = array(), array $processors = array()) + public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null) { $this->name = $name; $this->setHandlers($handlers); $this->processors = $processors; + $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC'); + + if (\PHP_VERSION_ID >= 80100) { + // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412 + /** @var \WeakMap<\Fiber, int> $fiberLogDepth */ + $fiberLogDepth = new \WeakMap(); + $this->fiberLogDepth = $fiberLogDepth; + } } - /** - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } /** * Return a new cloned instance with the name changed - * - * @return static */ - public function withName($name) + public function withName(string $name): self { $new = clone $this; $new->name = $name; @@ -174,11 +221,8 @@ public function withName($name) /** * Pushes a handler on to the stack. - * - * @param HandlerInterface $handler - * @return $this */ - public function pushHandler(HandlerInterface $handler) + public function pushHandler(HandlerInterface $handler): self { array_unshift($this->handlers, $handler); @@ -188,9 +232,9 @@ public function pushHandler(HandlerInterface $handler) /** * Pops a handler from the stack * - * @return HandlerInterface + * @throws \LogicException If empty handler stack */ - public function popHandler() + public function popHandler(): HandlerInterface { if (!$this->handlers) { throw new \LogicException('You tried to pop from an empty handler stack.'); @@ -204,12 +248,11 @@ public function popHandler() * * If a map is passed, keys will be ignored. * - * @param HandlerInterface[] $handlers - * @return $this + * @param HandlerInterface[] $handlers */ - public function setHandlers(array $handlers) + public function setHandlers(array $handlers): self { - $this->handlers = array(); + $this->handlers = []; foreach (array_reverse($handlers) as $handler) { $this->pushHandler($handler); } @@ -220,22 +263,16 @@ public function setHandlers(array $handlers) /** * @return HandlerInterface[] */ - public function getHandlers() + public function getHandlers(): array { return $this->handlers; } /** * Adds a processor on to the stack. - * - * @param callable $callback - * @return $this */ - public function pushProcessor($callback) + public function pushProcessor(callable $callback): self { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } array_unshift($this->processors, $callback); return $this; @@ -244,9 +281,10 @@ public function pushProcessor($callback) /** * Removes the processor on top of the stack and returns it. * + * @throws \LogicException If empty processor stack * @return callable */ - public function popProcessor() + public function popProcessor(): callable { if (!$this->processors) { throw new \LogicException('You tried to pop from an empty processor stack.'); @@ -258,7 +296,7 @@ public function popProcessor() /** * @return callable[] */ - public function getProcessors() + public function getProcessors(): array { return $this->processors; } @@ -267,91 +305,118 @@ public function getProcessors() * Control the use of microsecond resolution timestamps in the 'datetime' * member of new records. * - * Generating microsecond resolution timestamps by calling - * microtime(true), formatting the result via sprintf() and then parsing - * the resulting string via \DateTime::createFromFormat() can incur - * a measurable runtime overhead vs simple usage of DateTime to capture - * a second resolution timestamp in systems which generate a large number - * of log events. + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. * * @param bool $micro True to use microtime() to create timestamps */ - public function useMicrosecondTimestamps($micro) + public function useMicrosecondTimestamps(bool $micro): self { - $this->microsecondTimestamps = (bool) $micro; + $this->microsecondTimestamps = $micro; + + return $this; + } + + public function useLoggingLoopDetection(bool $detectCycles): self + { + $this->detectCycles = $detectCycles; + + return $this; } /** * Adds a log record. * - * @param int $level The logging level - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param int $level The logging level (a Monolog or RFC 5424 level) + * @param string $message The log message + * @param mixed[] $context The log context + * @param DateTimeImmutable $datetime Optional log date to log into the past or future + * @return bool Whether the record has been processed + * + * @phpstan-param Level $level */ - public function addRecord($level, $message, array $context = array()) + public function addRecord(int $level, string $message, array $context = [], ?DateTimeImmutable $datetime = null): bool { - if (!$this->handlers) { - $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; } - $levelName = static::getLevelName($level); - - // check if any handler will handle this message so we can return early and save cycles - $handlerKey = null; - reset($this->handlers); - while ($handler = current($this->handlers)) { - if ($handler->isHandling(array('level' => $level))) { - $handlerKey = key($this->handlers); - break; + if ($this->detectCycles) { + if (\PHP_VERSION_ID >= 80100 && $fiber = \Fiber::getCurrent()) { + // @phpstan-ignore offsetAssign.dimType + $this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0; + $logDepth = ++$this->fiberLogDepth[$fiber]; + } else { + $logDepth = ++$this->logDepth; } - - next($this->handlers); + } else { + $logDepth = 0; } - if (null === $handlerKey) { + if ($logDepth === 3) { + $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); + return false; + } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above return false; } - if (!static::$timezone) { - static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); - } - - // php7.1+ always has microseconds enabled, so we do not need this hack - if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) { - $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone); - } else { - $ts = new \DateTime('now', static::$timezone); - } - $ts->setTimezone(static::$timezone); + try { + $record = null; + + foreach ($this->handlers as $handler) { + if (null === $record) { + // skip creating the record as long as no handler is going to handle it + if (!$handler->isHandling(['level' => $level])) { + continue; + } + + $levelName = static::getLevelName($level); + + $record = [ + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => $levelName, + 'channel' => $this->name, + 'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + 'extra' => [], + ]; + + try { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } - $record = array( - 'message' => (string) $message, - 'context' => $context, - 'level' => $level, - 'level_name' => $levelName, - 'channel' => $this->name, - 'datetime' => $ts, - 'extra' => array(), - ); + // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted + try { + if (true === $handler->handle($record)) { + break; + } + } catch (Throwable $e) { + $this->handleException($e, $record); - try { - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); + return true; + } } - - while ($handler = current($this->handlers)) { - if (true === $handler->handle($record)) { - break; + } finally { + if ($this->detectCycles) { + if (isset($fiber)) { + $this->fiberLogDepth[$fiber]--; + } else { + $this->logDepth--; } - - next($this->handlers); } - } catch (Exception $e) { - $this->handleException($e, $record); } - return true; + return null !== $record; } /** @@ -364,12 +429,10 @@ public function addRecord($level, $message, array $context = array()) * This is useful at the end of a request and will be called automatically on every handler * when they get destructed. */ - public function close() + public function close(): void { foreach ($this->handlers as $handler) { - if (method_exists($handler, 'close')) { - $handler->close(); - } + $handler->close(); } } @@ -383,7 +446,7 @@ public function close() * have a long running process like a worker or an application server serving multiple requests * in one process. */ - public function reset() + public function reset(): void { foreach ($this->handlers as $handler) { if ($handler instanceof ResettableInterface) { @@ -398,108 +461,13 @@ public function reset() } } - /** - * Adds a log record at the DEBUG level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addDebug($message, array $context = array()) - { - return $this->addRecord(static::DEBUG, $message, $context); - } - - /** - * Adds a log record at the INFO level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addInfo($message, array $context = array()) - { - return $this->addRecord(static::INFO, $message, $context); - } - - /** - * Adds a log record at the NOTICE level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addNotice($message, array $context = array()) - { - return $this->addRecord(static::NOTICE, $message, $context); - } - - /** - * Adds a log record at the WARNING level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addWarning($message, array $context = array()) - { - return $this->addRecord(static::WARNING, $message, $context); - } - - /** - * Adds a log record at the ERROR level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addError($message, array $context = array()) - { - return $this->addRecord(static::ERROR, $message, $context); - } - - /** - * Adds a log record at the CRITICAL level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addCritical($message, array $context = array()) - { - return $this->addRecord(static::CRITICAL, $message, $context); - } - - /** - * Adds a log record at the ALERT level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addAlert($message, array $context = array()) - { - return $this->addRecord(static::ALERT, $message, $context); - } - - /** - * Adds a log record at the EMERGENCY level. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed - */ - public function addEmergency($message, array $context = array()) - { - return $this->addRecord(static::EMERGENCY, $message, $context); - } - /** * Gets all supported logging levels. * - * @return array Assoc array with human-readable level names => level codes. + * @return array Assoc array with human-readable level names => level codes. + * @phpstan-return array */ - public static function getLevels() + public static function getLevels(): array { return array_flip(static::$levels); } @@ -507,10 +475,12 @@ public static function getLevels() /** * Gets the name of the logging level. * - * @param int $level - * @return string + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level $level + * @phpstan-return LevelName */ - public static function getLevelName($level) + public static function getLevelName(int $level): string { if (!isset(static::$levels[$level])) { throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); @@ -522,18 +492,32 @@ public static function getLevelName($level) /** * Converts PSR-3 levels to Monolog ones if necessary * - * @param string|int $level Level number (monolog) or name (PSR-3) - * @return int + * @param string|int $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param Level|LevelName|LogLevel::* $level + * @phpstan-return Level */ - public static function toMonologLevel($level) + public static function toMonologLevel($level): int { if (is_string($level)) { + if (is_numeric($level)) { + /** @phpstan-ignore-next-line */ + return intval($level); + } + // Contains chars of all log levels and avoids using strtoupper() which may have - // strange results depending on locale (for example, "i" will become "İ") + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY'); if (defined(__CLASS__.'::'.$upper)) { return constant(__CLASS__ . '::' . $upper); } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); + } + + if (!is_int($level)) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels)); } return $level; @@ -542,14 +526,13 @@ public static function toMonologLevel($level) /** * Checks whether the Logger has a handler that listens on the given level * - * @param int $level - * @return bool + * @phpstan-param Level $level */ - public function isHandling($level) + public function isHandling(int $level): bool { - $record = array( + $record = [ 'level' => $level, - ); + ]; foreach ($this->handlers as $handler) { if ($handler->isHandling($record)) { @@ -561,57 +544,46 @@ public function isHandling($level) } /** - * Set a custom exception handler + * Set a custom exception handler that will be called if adding a new record fails * - * @param callable $callback - * @return $this + * The callable will receive an exception object and the record that failed to be logged */ - public function setExceptionHandler($callback) + public function setExceptionHandler(?callable $callback): self { - if (!is_callable($callback)) { - throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); - } $this->exceptionHandler = $callback; return $this; } - /** - * @return callable - */ - public function getExceptionHandler() + public function getExceptionHandler(): ?callable { return $this->exceptionHandler; } - /** - * Delegates exception management to the custom exception handler, - * or throws the exception if no custom handler is set. - */ - protected function handleException(Exception $e, array $record) - { - if (!$this->exceptionHandler) { - throw $e; - } - - call_user_func($this->exceptionHandler, $e, $record); - } - /** * Adds a log record at an arbitrary level. * * This method allows for compatibility with common interfaces. * - * @param mixed $level The log level - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + * + * @phpstan-param Level|LevelName|LogLevel::* $level */ - public function log($level, $message, array $context = array()) + public function log($level, $message, array $context = []): void { + if (!is_int($level) && !is_string($level)) { + throw new \InvalidArgumentException('$level is expected to be a string or int'); + } + + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + $level = static::toMonologLevel($level); - return $this->addRecord($level, $message, $context); + $this->addRecord($level, (string) $message, $context); } /** @@ -619,13 +591,12 @@ public function log($level, $message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function debug($message, array $context = array()) + public function debug($message, array $context = []): void { - return $this->addRecord(static::DEBUG, $message, $context); + $this->addRecord(static::DEBUG, (string) $message, $context); } /** @@ -633,13 +604,12 @@ public function debug($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function info($message, array $context = array()) + public function info($message, array $context = []): void { - return $this->addRecord(static::INFO, $message, $context); + $this->addRecord(static::INFO, (string) $message, $context); } /** @@ -647,13 +617,12 @@ public function info($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function notice($message, array $context = array()) + public function notice($message, array $context = []): void { - return $this->addRecord(static::NOTICE, $message, $context); + $this->addRecord(static::NOTICE, (string) $message, $context); } /** @@ -661,136 +630,133 @@ public function notice($message, array $context = array()) * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function warn($message, array $context = array()) + public function warning($message, array $context = []): void { - return $this->addRecord(static::WARNING, $message, $context); + $this->addRecord(static::WARNING, (string) $message, $context); } /** - * Adds a log record at the WARNING level. + * Adds a log record at the ERROR level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function warning($message, array $context = array()) + public function error($message, array $context = []): void { - return $this->addRecord(static::WARNING, $message, $context); + $this->addRecord(static::ERROR, (string) $message, $context); } /** - * Adds a log record at the ERROR level. + * Adds a log record at the CRITICAL level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function err($message, array $context = array()) + public function critical($message, array $context = []): void { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::CRITICAL, (string) $message, $context); } /** - * Adds a log record at the ERROR level. + * Adds a log record at the ALERT level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function error($message, array $context = array()) + public function alert($message, array $context = []): void { - return $this->addRecord(static::ERROR, $message, $context); + $this->addRecord(static::ALERT, (string) $message, $context); } /** - * Adds a log record at the CRITICAL level. + * Adds a log record at the EMERGENCY level. * * This method allows for compatibility with common interfaces. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param string|Stringable $message The log message + * @param mixed[] $context The log context */ - public function crit($message, array $context = array()) + public function emergency($message, array $context = []): void { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->addRecord(static::EMERGENCY, (string) $message, $context); } /** - * Adds a log record at the CRITICAL level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * Sets the timezone to be used for the timestamp of log records. */ - public function critical($message, array $context = array()) + public function setTimezone(DateTimeZone $tz): self { - return $this->addRecord(static::CRITICAL, $message, $context); + $this->timezone = $tz; + + return $this; } /** - * Adds a log record at the ALERT level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * Returns the timezone to be used for the timestamp of log records. */ - public function alert($message, array $context = array()) + public function getTimezone(): DateTimeZone { - return $this->addRecord(static::ALERT, $message, $context); + return $this->timezone; } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @param array $record + * @phpstan-param Record $record */ - public function emerg($message, array $context = array()) + protected function handleException(Throwable $e, array $record): void { - return $this->addRecord(static::EMERGENCY, $message, $context); + if (!$this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); } /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. - * - * @param string $message The log message - * @param array $context The log context - * @return bool Whether the record has been processed + * @return array */ - public function emergency($message, array $context = array()) + public function __serialize(): array { - return $this->addRecord(static::EMERGENCY, $message, $context); + return [ + 'name' => $this->name, + 'handlers' => $this->handlers, + 'processors' => $this->processors, + 'microsecondTimestamps' => $this->microsecondTimestamps, + 'timezone' => $this->timezone, + 'exceptionHandler' => $this->exceptionHandler, + 'logDepth' => $this->logDepth, + 'detectCycles' => $this->detectCycles, + ]; } /** - * Set the timezone to be used for the timestamp of log records. - * - * This is stored globally for all Logger instances - * - * @param \DateTimeZone $tz Timezone object + * @param array $data */ - public static function setTimezone(\DateTimeZone $tz) + public function __unserialize(array $data): void { - self::$timezone = $tz; + foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) { + if (isset($data[$property])) { + $this->$property = $data[$property]; + } + } + + if (\PHP_VERSION_ID >= 80100) { + // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412 + /** @var \WeakMap<\Fiber, int> $fiberLogDepth */ + $fiberLogDepth = new \WeakMap(); + $this->fiberLogDepth = $fiberLogDepth; + } } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php index cdf5ec73..6f618595 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class GitProcessor implements ProcessorInterface { + /** @var int */ private $level; - private static $cache; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -45,20 +55,23 @@ public function __invoke(array $record) return $record; } - private static function getGitInfo() + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array { if (self::$cache) { return self::$cache; } - $branches = `git branch -v --no-abbrev`; + $branches = shell_exec('git branch -v --no-abbrev'); if ($branches && preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { - return self::$cache = array( + return self::$cache = [ 'branch' => $matches[1], 'commit' => $matches[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 00000000..91fda7d6 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + /** @var string */ + private static $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array + { + $record['extra']['hostname'] = self::$host; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php index 6ae192a2..a32e76b2 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class IntrospectionProcessor implements ProcessorInterface { + /** @var int */ private $level; - + /** @var string[] */ private $skipClassesPartials; - + /** @var int */ private $skipStackFramesCount; - - private $skipFunctions = array( + /** @var string[] */ + private $skipFunctions = [ 'call_user_func', 'call_user_func_array', - ); + ]; - public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0) + /** + * @param string|int $level The minimum logging level at which this Processor will be triggered + * @param string[] $skipClassesPartials + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function __construct($level = Logger::DEBUG, array $skipClassesPartials = [], int $skipStackFramesCount = 0) { $this->level = Logger::toMonologLevel($level); - $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); $this->skipStackFramesCount = $skipStackFramesCount; } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { return $record; } - /* - * http://php.net/manual/en/function.debug-backtrace.php - * As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. - * Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. - */ - $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); // skip first since it's always the current method array_shift($trace); @@ -74,11 +79,13 @@ public function __invoke(array $record) foreach ($this->skipClassesPartials as $part) { if (strpos($trace[$i]['class'], $part) !== false) { $i++; + continue 2; } } } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) { $i++; + continue; } @@ -90,18 +97,22 @@ public function __invoke(array $record) // we should have the call source now $record['extra'] = array_merge( $record['extra'], - array( + [ 'file' => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null, 'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null, 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'callType' => isset($trace[$i]['type']) ? $trace[$i]['type'] : null, 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, - ) + ] ); return $record; } - private function isTraceClassOrSkippedFunction(array $trace, $index) + /** + * @param array[] $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool { if (!isset($trace[$index])) { return false; diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php index 0543e929..37c756fc 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -1,4 +1,4 @@ -realUsage); - $formatted = $this->formatBytes($bytes); + $usage = memory_get_peak_usage($this->realUsage); - $record['extra']['memory_peak_usage'] = $formatted; + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_peak_usage'] = $usage; return $record; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php index 2a379a30..227deb7c 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -1,4 +1,4 @@ -realUsage = (bool) $realUsage; - $this->useFormatting = (bool) $useFormatting; + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; } /** * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is * * @param int $bytes - * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int */ - protected function formatBytes($bytes) + protected function formatBytes(int $bytes) { - $bytes = (int) $bytes; - if (!$this->useFormatting) { return $bytes; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php index 2783d656..e141921e 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -1,4 +1,4 @@ -realUsage); - $formatted = $this->formatBytes($bytes); + $usage = memory_get_usage($this->realUsage); - $record['extra']['memory_usage'] = $formatted; + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record['extra']['memory_usage'] = $usage; return $record; } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php index 2f5b3265..239e4c8f 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -1,9 +1,9 @@ - + * (c) Jordi Boggiano * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -12,27 +12,37 @@ namespace Monolog\Processor; use Monolog\Logger; +use Psr\Log\LogLevel; /** * Injects Hg branch and Hg revision number in all records * * @author Jonathan A. Schweder + * + * @phpstan-import-type LevelName from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger */ class MercurialProcessor implements ProcessorInterface { + /** @var Level */ private $level; - private static $cache; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + /** + * @param int|string $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ public function __construct($level = Logger::DEBUG) { $this->level = Logger::toMonologLevel($level); } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // return if the level is not high enough if ($record['level'] < $this->level) { @@ -44,20 +54,24 @@ public function __invoke(array $record) return $record; } - private static function getMercurialInfo() + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array { if (self::$cache) { return self::$cache; } - $result = explode(' ', trim(`hg id -nb`)); + $result = explode(' ', trim((string) shell_exec('hg id -nb'))); + if (count($result) >= 3) { - return self::$cache = array( + return self::$cache = [ 'branch' => $result[1], 'revision' => $result[2], - ); + ]; } - return self::$cache = array(); + return self::$cache = []; } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php index 66b80fbb..3b939a95 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Record from \Monolog\Logger */ interface ProcessorInterface { /** - * @return array The processed records + * @return array The processed record + * + * @phpstan-param Record $record + * @phpstan-return Record */ - public function __invoke(array $records); + public function __invoke(array $record); } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php index a318af7e..e7c12176 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -1,4 +1,4 @@ -dateFormat = $dateFormat; $this->removeUsedContextFields = $removeUsedContextFields; } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { if (false === strpos($record['message'], '{')) { return $record; } - $replacements = array(); + $replacements = []; foreach ($record['context'] as $key => $val) { $placeholder = '{' . $key . '}'; if (strpos($record['message'], $placeholder) === false) { @@ -59,8 +58,16 @@ public function __invoke(array $record) if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { $replacements[$placeholder] = $val; - } elseif ($val instanceof \DateTime) { - $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } elseif ($val instanceof \DateTimeInterface) { + if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } else { + $replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE); + } + } elseif ($val instanceof \UnitEnum) { + $replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name; } elseif (is_object($val)) { $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; } elseif (is_array($val)) { diff --git a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php index 615a4d99..80f18747 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -1,4 +1,4 @@ -setTags($tags); } - public function addTags(array $tags = array()) + /** + * @param string[] $tags + */ + public function addTags(array $tags = []): self { $this->tags = array_merge($this->tags, $tags); + + return $this; } - public function setTags(array $tags = array()) + /** + * @param string[] $tags + */ + public function setTags(array $tags = []): self { $this->tags = $tags; + + return $this; } - public function __invoke(array $record) + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { $record['extra']['tags'] = $this->tags; diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php index d1f708cf..a27b74db 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -1,4 +1,4 @@ - 32 || $length < 1) { + if ($length > 32 || $length < 1) { throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); } - $this->uid = $this->generateUid($length); } - public function __invoke(array $record) + /** + * {@inheritDoc} + */ + public function __invoke(array $record): array { $record['extra']['uid'] = $this->uid; return $record; } - /** - * @return string - */ - public function getUid() + public function getUid(): string { return $this->uid; } @@ -52,8 +52,8 @@ public function reset() $this->uid = $this->generateUid(strlen($this->uid)); } - private function generateUid($length) + private function generateUid(int $length): string { - return substr(hash('md5', uniqid('', true)), 0, $length); + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); } } diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php index 2e8dfae1..887f4d39 100644 --- a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -1,4 +1,4 @@ -|\ArrayAccess */ protected $serverData; @@ -28,21 +28,22 @@ class WebProcessor implements ProcessorInterface * * Array is structured as [key in record.extra => key in $serverData] * - * @var array + * @var array */ - protected $extraFields = array( + protected $extraFields = [ 'url' => 'REQUEST_URI', 'ip' => 'REMOTE_ADDR', 'http_method' => 'REQUEST_METHOD', 'server' => 'SERVER_NAME', 'referrer' => 'HTTP_REFERER', - ); + 'user_agent' => 'HTTP_USER_AGENT', + ]; /** - * @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data - * @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer + * @param array|\ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data */ - public function __construct($serverData = null, array $extraFields = null) + public function __construct($serverData = null, ?array $extraFields = null) { if (null === $serverData) { $this->serverData = &$_SERVER; @@ -52,28 +53,30 @@ public function __construct($serverData = null, array $extraFields = null) throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); } + $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; if (isset($this->serverData['UNIQUE_ID'])) { $this->extraFields['unique_id'] = 'UNIQUE_ID'; + $defaultEnabled[] = 'unique_id'; } - if (null !== $extraFields) { - if (isset($extraFields[0])) { - foreach (array_keys($this->extraFields) as $fieldName) { - if (!in_array($fieldName, $extraFields)) { - unset($this->extraFields[$fieldName]); - } + if (null === $extraFields) { + $extraFields = $defaultEnabled; + } + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields)) { + unset($this->extraFields[$fieldName]); } - } else { - $this->extraFields = $extraFields; } + } else { + $this->extraFields = $extraFields; } } /** - * @param array $record - * @return array + * {@inheritDoc} */ - public function __invoke(array $record) + public function __invoke(array $record): array { // skip processing if for some reason request data // is not present (CLI or wonky SAPIs) @@ -86,12 +89,7 @@ public function __invoke(array $record) return $record; } - /** - * @param string $extraName - * @param string $serverName - * @return $this - */ - public function addExtraField($extraName, $serverName) + public function addExtraField(string $extraName, string $serverName): self { $this->extraFields[$extraName] = $serverName; @@ -99,13 +97,13 @@ public function addExtraField($extraName, $serverName) } /** - * @param array $extra - * @return array + * @param mixed[] $extra + * @return mixed[] */ - private function appendExtraFields(array $extra) + private function appendExtraFields(array $extra): array { foreach ($this->extraFields as $extraName => $serverName) { - $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null; + $extra[$extraName] = $this->serverData[$serverName] ?? null; } return $extra; diff --git a/vendor/monolog/monolog/src/Monolog/Registry.php b/vendor/monolog/monolog/src/Monolog/Registry.php index 159b751c..ae94ae6c 100644 --- a/vendor/monolog/monolog/src/Monolog/Registry.php +++ b/vendor/monolog/monolog/src/Monolog/Registry.php @@ -1,4 +1,4 @@ -addError('Sent to $api Logger instance'); - * Monolog\Registry::application()->addError('Sent to $application Logger instance'); + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); * } * * @@ -42,7 +42,7 @@ class Registry * * @var Logger[] */ - private static $loggers = array(); + private static $loggers = []; /** * Adds new logging channel to the registry @@ -51,8 +51,9 @@ class Registry * @param string|null $name Name of the logging channel ($logger->getName() by default) * @param bool $overwrite Overwrite instance in the registry if the given name already exists? * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + * @return void */ - public static function addLogger(Logger $logger, $name = null, $overwrite = false) + public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false) { $name = $name ?: $logger->getName(); @@ -68,15 +69,15 @@ public static function addLogger(Logger $logger, $name = null, $overwrite = fals * * @param string|Logger $logger Name or logger instance */ - public static function hasLogger($logger) + public static function hasLogger($logger): bool { if ($logger instanceof Logger) { $index = array_search($logger, self::$loggers, true); return false !== $index; - } else { - return isset(self::$loggers[$logger]); } + + return isset(self::$loggers[$logger]); } /** @@ -84,7 +85,7 @@ public static function hasLogger($logger) * * @param string|Logger $logger Name or logger instance */ - public static function removeLogger($logger) + public static function removeLogger($logger): void { if ($logger instanceof Logger) { if (false !== ($idx = array_search($logger, self::$loggers, true))) { @@ -98,9 +99,9 @@ public static function removeLogger($logger) /** * Clears the registry */ - public static function clear() + public static function clear(): void { - self::$loggers = array(); + self::$loggers = []; } /** @@ -108,9 +109,8 @@ public static function clear() * * @param string $name Name of the requested Logger instance * @throws \InvalidArgumentException If named Logger instance is not in the registry - * @return Logger Requested instance of Logger */ - public static function getInstance($name) + public static function getInstance($name): Logger { if (!isset(self::$loggers[$name])) { throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); @@ -123,7 +123,7 @@ public static function getInstance($name) * Gets Logger instance from the registry via static method call * * @param string $name Name of the requested Logger instance - * @param array $arguments Arguments passed to static method call + * @param mixed[] $arguments Arguments passed to static method call * @throws \InvalidArgumentException If named Logger instance is not in the registry * @return Logger Requested instance of Logger */ diff --git a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php index 635bc77d..2c5fd785 100644 --- a/vendor/monolog/monolog/src/Monolog/ResettableInterface.php +++ b/vendor/monolog/monolog/src/Monolog/ResettableInterface.php @@ -1,4 +1,4 @@ - + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ class SignalHandler { + /** @var LoggerInterface */ private $logger; - private $previousSignalHandler = array(); - private $signalLevelMap = array(); - private $signalRestartSyscalls = array(); + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private $previousSignalHandler = []; + /** @var array */ + private $signalLevelMap = []; + /** @var array */ + private $signalRestartSyscalls = []; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } - public function registerSignalHandler($signo, $level = LogLevel::CRITICAL, $callPrevious = true, $restartSyscalls = true, $async = true) + /** + * @param int|string $level Level or level name + * @param bool $callPrevious + * @param bool $restartSyscalls + * @param bool|null $async + * @return $this + * + * @phpstan-param Level|LevelName|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self { if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { return $this; } + $level = Logger::toMonologLevel($level); + if ($callPrevious) { - if (function_exists('pcntl_signal_get_handler')) { - $handler = pcntl_signal_get_handler($signo); - if ($handler === false) { - return $this; - } - $this->previousSignalHandler[$signo] = $handler; - } else { - $this->previousSignalHandler[$signo] = true; - } + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; } else { unset($this->previousSignalHandler[$signo]); } $this->signalLevelMap[$signo] = $level; $this->signalRestartSyscalls[$signo] = $restartSyscalls; - if (function_exists('pcntl_async_signals') && $async !== null) { + if ($async !== null) { pcntl_async_signals($async); } - pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); return $this; } - public function handleSignal($signo, array $siginfo = null) + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void { - static $signals = array(); + /** @var array $signals */ + static $signals = []; if (!$signals && extension_loaded('pcntl')) { $pcntl = new ReflectionExtension('pcntl'); - $constants = $pcntl->getConstants(); - if (!$constants) { - // HHVM 3.24.2 returns an empty array. - $constants = get_defined_constants(true); - $constants = $constants['Core']; - } - foreach ($constants as $name => $value) { + // HHVM 3.24.2 returns an empty array. + foreach ($pcntl->getConstants() ?: get_defined_constants(true)['Core'] as $name => $value) { if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { $signals[$value] = $name; } } - unset($constants); } - $level = isset($this->signalLevelMap[$signo]) ? $this->signalLevelMap[$signo] : LogLevel::CRITICAL; - $signal = isset($signals[$signo]) ? $signals[$signo] : $signo; - $context = isset($siginfo) ? $siginfo : array(); + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); if (!isset($this->previousSignalHandler[$signo])) { return; } - if ($this->previousSignalHandler[$signo] === true || $this->previousSignalHandler[$signo] === SIG_DFL) { + if ($this->previousSignalHandler[$signo] === SIG_DFL) { if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') - && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill')) { - $restartSyscalls = isset($this->signalRestartSyscalls[$signo]) ? $this->signalRestartSyscalls[$signo] : true; - pcntl_signal($signo, SIG_DFL, $restartSyscalls); - pcntl_sigprocmask(SIG_UNBLOCK, array($signo), $oldset); - posix_kill(posix_getpid(), $signo); - pcntl_signal_dispatch(); - pcntl_sigprocmask(SIG_SETMASK, $oldset); - pcntl_signal($signo, array($this, 'handleSignal'), $restartSyscalls); - } - } elseif (is_callable($this->previousSignalHandler[$signo])) { - if (PHP_VERSION_ID >= 70100) { - $this->previousSignalHandler[$signo]($signo, $siginfo); - } else { - $this->previousSignalHandler[$signo]($signo); + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') + ) { + $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); } } } diff --git a/vendor/monolog/monolog/src/Monolog/Test/TestCase.php b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 00000000..bc0b425e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Test/TestCase.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @phpstan-import-type Record from \Monolog\Logger + * @phpstan-import-type Level from \Monolog\Logger + * + * @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677 + */ +class TestCase extends \PHPUnit\Framework\TestCase +{ + public function tearDown(): void + { + parent::tearDown(); + + if (isset($this->handler)) { + unset($this->handler); + } + } + + /** + * @param mixed[] $context + * + * @return array Record + * + * @phpstan-param Level $level + * @phpstan-return Record + */ + protected function getRecord(int $level = Logger::WARNING, string $message = 'test', array $context = []): array + { + return [ + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => new DateTimeImmutable(true), + 'extra' => [], + ]; + } + + /** + * @phpstan-return Record[] + */ + protected function getMultipleRecords(): array + { + return [ + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { + return $record['message']; + })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Utils.php b/vendor/monolog/monolog/src/Monolog/Utils.php index 7f1ba129..d4ff4c04 100644 --- a/vendor/monolog/monolog/src/Monolog/Utils.php +++ b/vendor/monolog/monolog/src/Monolog/Utils.php @@ -1,4 +1,4 @@ -=')) { - $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; } if ($ignoreErrors) { @@ -90,16 +104,16 @@ public static function jsonEncode($data, $encodeFlags = null, $ignoreErrors = fa * * If the failure is due to invalid string encoding, try to clean the * input and encode again. If the second encoding attempt fails, the - * inital error is not encoding related or the input can't be cleaned then + * initial error is not encoding related or the input can't be cleaned then * raise a descriptive exception. * - * @param int $code return code of json_last_error function - * @param mixed $data data that was meant to be encoded - * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION * @throws \RuntimeException if failure can't be corrected * @return string JSON encoded data after error correction */ - public static function handleJsonError($code, $data, $encodeFlags = null) + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string { if ($code !== JSON_ERROR_UTF8) { self::throwEncodeError($code, $data); @@ -113,8 +127,8 @@ public static function handleJsonError($code, $data, $encodeFlags = null) self::throwEncodeError($code, $data); } - if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) { - $encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; } $json = json_encode($data, $encodeFlags); @@ -126,14 +140,35 @@ public static function handleJsonError($code, $data, $encodeFlags = null) return $json; } + /** + * @internal + */ + public static function pcreLastErrorMessage(int $code): string + { + if (PHP_VERSION_ID >= 80000) { + return preg_last_error_msg(); + } + + $constants = (get_defined_constants(true))['pcre']; + $constants = array_filter($constants, function ($key) { + return substr($key, -6) == '_ERROR'; + }, ARRAY_FILTER_USE_KEY); + + $constants = array_flip($constants); + + return $constants[$code] ?? 'UNDEFINED_ERROR'; + } + /** * Throws an exception according to a given code with a customized message * * @param int $code return code of json_last_error function * @param mixed $data data that was meant to be encoded * @throws \RuntimeException + * + * @return never */ - private static function throwEncodeError($code, $data) + private static function throwEncodeError(int $code, $data): void { switch ($code) { case JSON_ERROR_DEPTH: @@ -169,21 +204,81 @@ private static function throwEncodeError($code, $data) * can be used as a callback for array_walk_recursive. * * @param mixed $data Input to check and convert if needed, passed by ref - * @private */ - public static function detectAndCleanUtf8(&$data) + private static function detectAndCleanUtf8(&$data): void { if (is_string($data) && !preg_match('//u', $data)) { $data = preg_replace_callback( '/[\x80-\xFF]+/', - function ($m) { return utf8_encode($m[0]); }, + function ($m) { + return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); + }, $data ); + if (!is_string($data)) { + $pcreErrorCode = preg_last_error(); + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); + } $data = str_replace( - array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'), - array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'), + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], $data ); } } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (!preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match)) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'])) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + /** + * @param array $record + */ + public static function getRecordMessageForException(array $record): string + { + $context = ''; + $extra = ''; + try { + if ($record['context']) { + $context = "\nContext: " . json_encode($record['context']); + } + if ($record['extra']) { + $extra = "\nExtra: " . json_encode($record['extra']); + } + } catch (\Throwable $e) { + // noop + } + + return "\nThe exception occurred while attempting to log: " . $record['message'] . $context . $extra; + } } From f989de03e562ce9387d33e18c8f37be856c0d738 Mon Sep 17 00:00:00 2001 From: asuquoe62-star Date: Tue, 21 Apr 2026 16:13:11 -0400 Subject: [PATCH 12/13] Update htdocs/export/export_word.php Co-authored-by: Mitchell Rysavy --- htdocs/export/export_word.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/export/export_word.php b/htdocs/export/export_word.php index 8ecb73a5..18b3c7e9 100755 --- a/htdocs/export/export_word.php +++ b/htdocs/export/export_word.php @@ -2,7 +2,7 @@ # # Exports the given HTML content as word document # -include("../includes/db_lib.php"); +require_once("../includes/db_lib.php"); require_once(__DIR__."/word_export_lib.php"); putUILog('export_word', 'X', basename($_SERVER['REQUEST_URI'], ".php"), 'X', 'X', 'X'); From 4e1b93d17e097d163de6812f7e95bf1cd0ff32e9 Mon Sep 17 00:00:00 2001 From: Emmanuel Asuquo Date: Thu, 23 Apr 2026 21:15:31 -0400 Subject: [PATCH 13/13] made some changes to word_export_lib.php --- htdocs/export/word_export_lib.php | 46 +++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/htdocs/export/word_export_lib.php b/htdocs/export/word_export_lib.php index 96b909b0..3a9a99b1 100644 --- a/htdocs/export/word_export_lib.php +++ b/htdocs/export/word_export_lib.php @@ -31,15 +31,34 @@ function blis_word_send_legacy_doc($html_fragment, $file_prefix) $file_name = blis_word_sanitize_filename_segment($file_prefix).'_'.$date.'.doc'; header('Content-Type: application/vnd.ms-word'); header('Content-Disposition: attachment; filename="'.$file_name.'"'); + $safe_html = blis_word_sanitize_legacy_html($html_fragment); echo "\n"; - echo $html_fragment; + echo $safe_html; echo ""; exit; } +function blis_word_sanitize_legacy_html($html_fragment) +{ + $safe = (string)$html_fragment; + + # Remove high-risk executable elements. + $safe = preg_replace('/<\s*(script|iframe|object|embed|applet|meta|link|style)\b[^>]*>.*?<\s*\/\s*\1\s*>/is', '', $safe); + $safe = preg_replace('/<\s*(script|iframe|object|embed|applet|meta|link|style)\b[^>]*\/?\s*>/is', '', $safe); + + # Remove inline JS event handlers (onclick, onload, etc.). + $safe = preg_replace('/\s+on[a-z]+\s*=\s*(".*?"|\'.*?\'|[^\s>]+)/is', '', $safe); + + # Remove javascript: and data: URL payloads from common attributes. + $safe = preg_replace('/\s+(href|src|xlink:href)\s*=\s*("|\')\s*(javascript:|data:)[^"\']*\2/is', '', $safe); + $safe = preg_replace('/\s+(href|src|xlink:href)\s*=\s*(javascript:|data:)[^\s>]*/is', '', $safe); + + return $safe; +} + function blis_word_export_docx($html_fragment, $file_prefix) { - $pandoc_bin = trim((string)shell_exec('command -v pandoc')); + $pandoc_bin = blis_word_find_pandoc_bin(); if($pandoc_bin === '') { return false; @@ -86,8 +105,31 @@ function blis_word_export_docx($html_fragment, $file_prefix) header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document'); header('Content-Disposition: attachment; filename="'.$file_name.'"'); header('Content-Length: '.filesize($tmp_docx)); + + # Prevent buffered warnings/whitespace from corrupting binary docx output. + while(ob_get_level() > 0) + { + ob_end_clean(); + } + readfile($tmp_docx); @unlink($tmp_docx); exit; } +function blis_word_find_pandoc_bin() +{ + if(PHP_OS_FAMILY === 'Windows') + { + $output = array(); + $exit_code = 0; + exec('where pandoc', $output, $exit_code); + if($exit_code === 0 && isset($output[0])) + { + return trim($output[0]); + } + return ''; + } + + return trim((string)shell_exec('command -v pandoc')); +}